Enhance audience management features with new components and API integrations
- Added `AssignCoursesModal`, `AudienceMemberRow`, `AudienceTable`, and related components for improved audience management UI. - Implemented audience invite actions including resend and revoke functionality. - Updated audience API to support importing members and assigning courses. - Refactored audience data handling to include status and invite management. - Improved overall user experience with pagination and selection features in the audience table.
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
<script lang="ts">
|
||||
import * as Avatar from '@cio/ui/base/avatar';
|
||||
import TrashIcon from '@lucide/svelte/icons/trash';
|
||||
import UserIcon from '@lucide/svelte/icons/user';
|
||||
import { calDateDiff } from '$lib/utils/functions/date';
|
||||
import { IconButton } from '@cio/ui/custom/icon-button';
|
||||
import { shortenName } from '$lib/utils/functions/string';
|
||||
import { Vote } from '$features/ui';
|
||||
import { CircleCheckIcon } from '$features/ui/icons';
|
||||
import { SafeHtmlContent } from '@cio/ui/custom/safe-html-content';
|
||||
@@ -40,11 +40,15 @@
|
||||
<header class="flex items-center justify-between p-2 leading-none">
|
||||
<div class="flex items-center text-black">
|
||||
<Avatar.Root class="size-7">
|
||||
<Avatar.Image
|
||||
src={comment.author?.avatarUrl ? comment.author.avatarUrl : '/logo-192.png'}
|
||||
alt={comment.author?.fullname ? comment.author.fullname : 'User'}
|
||||
/>
|
||||
<Avatar.Fallback>{shortenName(comment.author?.fullname) || 'U'}</Avatar.Fallback>
|
||||
{#if comment.author?.avatarUrl}
|
||||
<Avatar.Image
|
||||
src={comment.author.avatarUrl}
|
||||
alt={comment.author?.fullname ? comment.author.fullname : 'User'}
|
||||
/>
|
||||
{/if}
|
||||
<Avatar.Fallback>
|
||||
<UserIcon class="ui:size-3.5 ui:text-muted-foreground" />
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
|
||||
<p class="ml-2 text-sm dark:text-white">{comment.author?.fullname}</p>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import * as Avatar from '@cio/ui/base/avatar';
|
||||
import { Skeleton } from '@cio/ui/base/skeleton';
|
||||
import TrashIcon from '@lucide/svelte/icons/trash';
|
||||
import UserIcon from '@lucide/svelte/icons/user';
|
||||
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
import { calDateDiff } from '$lib/utils/functions/date';
|
||||
@@ -17,19 +18,20 @@
|
||||
import { Vote } from '$features/ui';
|
||||
import { IconButton } from '@cio/ui/custom/icon-button';
|
||||
import { InputField } from '@cio/ui/custom/input-field';
|
||||
import { shortenName } from '$lib/utils/functions/string';
|
||||
import { TextEditor } from '$features/ui';
|
||||
import { CommunityDeleteModal, CommunityCommentItem } from '$features/community/components';
|
||||
import { BackButton } from '@cio/ui';
|
||||
import type { CommunityQuestionSuccess } from '../utils/types';
|
||||
import { currentCommunityQuestion } from '../utils/store';
|
||||
import { SafeHtmlContent } from '@cio/ui/custom/safe-html-content';
|
||||
|
||||
interface Props {
|
||||
slug: string;
|
||||
backHref?: string;
|
||||
question?: CommunityQuestionSuccess['data'] | null;
|
||||
}
|
||||
|
||||
let { question }: Props = $props();
|
||||
let { question, backHref }: Props = $props();
|
||||
|
||||
let commentEditor: TiptapEditor | undefined = $state();
|
||||
let isValidAnswer = false; // V2 allow admin mark an answer as accepted
|
||||
@@ -100,6 +102,9 @@
|
||||
<Page.Root class="mx-auto w-[90%] md:mx-10 md:max-w-3xl lg:mb-20">
|
||||
<Page.Header>
|
||||
<Page.HeaderContent>
|
||||
{#if backHref}
|
||||
<BackButton href={backHref} label={$t('community.ask.go_back')} />
|
||||
{/if}
|
||||
{#if communityApi.isEditMode}
|
||||
<div class="flex w-full items-center gap-2">
|
||||
<InputField
|
||||
@@ -175,13 +180,15 @@
|
||||
<div class="flex items-center justify-between p-2 leading-none">
|
||||
<div class="flex items-center text-black no-underline hover:underline">
|
||||
<Avatar.Root class="h-7 w-7">
|
||||
<Avatar.Image
|
||||
src={communityApi.question?.authorAvatarUrl
|
||||
? communityApi.question?.authorAvatarUrl
|
||||
: '/logo-192.png'}
|
||||
alt={communityApi.question?.authorFullname ? communityApi.question?.authorFullname : 'User'}
|
||||
/>
|
||||
<Avatar.Fallback>{shortenName(communityApi.question?.authorFullname) || 'U'}</Avatar.Fallback>
|
||||
{#if communityApi.question?.authorAvatarUrl}
|
||||
<Avatar.Image
|
||||
src={communityApi.question.authorAvatarUrl}
|
||||
alt={communityApi.question?.authorFullname ? communityApi.question.authorFullname : 'User'}
|
||||
/>
|
||||
{/if}
|
||||
<Avatar.Fallback>
|
||||
<UserIcon class="ui:size-3.5 ui:text-muted-foreground" />
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
<p class="ml-2 text-sm dark:text-white">{communityApi.question?.authorFullname}</p>
|
||||
<p class="ml-2 text-sm text-gray-500 dark:text-white">
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
import { Empty } from '@cio/ui/custom/empty';
|
||||
import * as Item from '@cio/ui/base/item';
|
||||
import { Search } from '@cio/ui/custom/search';
|
||||
import { Button } from '@cio/ui/base/button';
|
||||
import { resolve } from '$app/paths';
|
||||
import MessageSquareMoreIcon from '@lucide/svelte/icons/message-square-more';
|
||||
import MessageCirclePlusIcon from '@lucide/svelte/icons/message-circle-plus';
|
||||
|
||||
@@ -62,10 +64,8 @@
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
<Select.Item value="">{$t('community.all')}</Select.Item>
|
||||
{#each communityApi.courses as course}
|
||||
{#if course.id}
|
||||
<Select.Item value={course.id}>{course.title}</Select.Item>
|
||||
{/if}
|
||||
{#each communityApi.courses as course (course.id)}
|
||||
<Select.Item value={course.id}>{course.title}</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
@@ -77,10 +77,10 @@
|
||||
<CommunityListLoader />
|
||||
<CommunityListLoader />
|
||||
{:else}
|
||||
{#each filteredQuestions as question}
|
||||
{#each filteredQuestions as question (question.slug)}
|
||||
<Item.Root variant="outline">
|
||||
{#snippet child({ props })}
|
||||
<a href="{$basePath}/community/{question.slug}" {...props}>
|
||||
<a href={resolve(`${$basePath}/community/[slug]`, { slug: question.slug })} {...props}>
|
||||
<Vote value={question.votes} />
|
||||
<Item.Content class="gap-y-0.5">
|
||||
<Item.Title class="mt-0">
|
||||
@@ -89,11 +89,15 @@
|
||||
<Item.Description>
|
||||
{question?.authorFullname} asked {calDateDiff(question.createdAt || new Date())}
|
||||
</Item.Description>
|
||||
<a class="m-0" href="/courses/{question.courseId}" onclick={(e) => e.stopPropagation()}>
|
||||
<Button
|
||||
class="m-0! justify-start! px-0! py-0! underline"
|
||||
href={resolve(`/courses/[id]`, { id: question.courseId })}
|
||||
variant="ghost"
|
||||
>
|
||||
<span class="text-muted-foreground p-0 text-xs">
|
||||
#{question?.courseTitle}
|
||||
</span>
|
||||
</a>
|
||||
</Button>
|
||||
</Item.Content>
|
||||
<Item.Actions>
|
||||
<div class="flex items-center">
|
||||
|
||||
Reference in New Issue
Block a user