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:
@@ -2,10 +2,10 @@
|
||||
import { untrack } from 'svelte';
|
||||
import * as DropdownMenu from '@cio/ui/base/dropdown-menu';
|
||||
import EllipsisVerticalIcon from '@lucide/svelte/icons/ellipsis-vertical';
|
||||
import UserIcon from '@lucide/svelte/icons/user';
|
||||
|
||||
import * as Avatar from '@cio/ui/base/avatar';
|
||||
import { TextareaField } from '@cio/ui/custom/textarea-field';
|
||||
import { shortenName } from '$lib/utils/functions/string';
|
||||
import { DeleteModal } from '$features/ui';
|
||||
import { Button } from '@cio/ui/base/button';
|
||||
|
||||
@@ -99,11 +99,12 @@
|
||||
<div>
|
||||
<div class="flex h-full items-start gap-3">
|
||||
<Avatar.Root class="mt-2 size-6!">
|
||||
<Avatar.Image
|
||||
src={$profile.avatarUrl ? $profile.avatarUrl : '/logo-192.png'}
|
||||
alt={$profile.fullname ? $profile.fullname : 'User'}
|
||||
/>
|
||||
<Avatar.Fallback>{shortenName($profile.fullname) || 'U'}</Avatar.Fallback>
|
||||
{#if $profile.avatarUrl}
|
||||
<Avatar.Image src={$profile.avatarUrl} alt={$profile.fullname ? $profile.fullname : 'User'} />
|
||||
{/if}
|
||||
<Avatar.Fallback>
|
||||
<UserIcon class="ui:size-3 ui:text-muted-foreground" />
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
<div class="h-full w-full">
|
||||
<TextareaField
|
||||
@@ -125,11 +126,12 @@
|
||||
{#each comments as commentItem (commentItem.id)}
|
||||
<div class="mt-2 flex items-start gap-3 pb-2">
|
||||
<Avatar.Root class="mt-2 size-6!">
|
||||
<Avatar.Image
|
||||
src={commentItem.avatar ? commentItem.avatar : '/logo-192.png'}
|
||||
alt={commentItem.name ? commentItem.name : 'User'}
|
||||
/>
|
||||
<Avatar.Fallback>{shortenName(commentItem.name) || 'U'}</Avatar.Fallback>
|
||||
{#if commentItem.avatar}
|
||||
<Avatar.Image src={commentItem.avatar} alt={commentItem.name ? commentItem.name : 'User'} />
|
||||
{/if}
|
||||
<Avatar.Fallback>
|
||||
<UserIcon class="ui:size-3 ui:text-muted-foreground" />
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
|
||||
<div class="w-full rounded-md border px-3 py-2 dark:border-neutral-700">
|
||||
|
||||
+54
-22
@@ -5,6 +5,7 @@
|
||||
import ChevronRightIcon from '@lucide/svelte/icons/chevron-right';
|
||||
import { CircleCheckIcon } from '$features/ui/icons';
|
||||
import { Button } from '@cio/ui/base/button';
|
||||
import * as Tooltip from '@cio/ui/base/tooltip';
|
||||
import { globalStore } from '$lib/utils/store/app';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
import { courseApi, lessonApi } from '$features/course/api';
|
||||
@@ -115,8 +116,25 @@
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const INTERACTIVE_TAGS = new Set(['INPUT', 'TEXTAREA', 'SELECT']);
|
||||
|
||||
function handleKeydown(event: KeyboardEvent) {
|
||||
const target = event.target as HTMLElement;
|
||||
if (INTERACTIVE_TAGS.has(target.tagName) || target.isContentEditable) return;
|
||||
|
||||
if (event.key === 'ArrowLeft' && !isPrevDisabled) {
|
||||
event.preventDefault();
|
||||
goToContent(prevNextContent.prev);
|
||||
} else if (event.key === 'ArrowRight' && !isNextDisabled) {
|
||||
event.preventDefault();
|
||||
goToContent(prevNextContent.next);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window onkeydown={handleKeydown} />
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
{#if showMarkComplete && lessonId}
|
||||
<Button
|
||||
@@ -131,27 +149,41 @@
|
||||
</Button>
|
||||
{/if}
|
||||
|
||||
<div class="flex items-center gap-1">
|
||||
<Button
|
||||
size="icon-sm"
|
||||
variant="outline"
|
||||
onclick={() => goToContent(prevNextContent.prev)}
|
||||
disabled={isPrevDisabled}
|
||||
aria-label={$t('course.navItem.lessons.prev')}
|
||||
title={$t('course.navItem.lessons.prev')}
|
||||
>
|
||||
<ChevronLeftIcon size={14} />
|
||||
</Button>
|
||||
<Tooltip.Provider>
|
||||
<div class="flex items-center gap-1">
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger>
|
||||
<Button
|
||||
size="icon-sm"
|
||||
variant="outline"
|
||||
onclick={() => goToContent(prevNextContent.prev)}
|
||||
disabled={isPrevDisabled}
|
||||
aria-label={$t('course.navItem.lessons.prev')}
|
||||
>
|
||||
<ChevronLeftIcon size={14} />
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="bottom" sideOffset={4}>
|
||||
{$t('course.navItem.lessons.prev_shortcut')}
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
|
||||
<Button
|
||||
size="icon-sm"
|
||||
variant="outline"
|
||||
onclick={() => goToContent(prevNextContent.next)}
|
||||
disabled={isNextDisabled}
|
||||
aria-label={$t('course.navItem.lessons.next')}
|
||||
title={$t('course.navItem.lessons.next')}
|
||||
>
|
||||
<ChevronRightIcon size={14} />
|
||||
</Button>
|
||||
</div>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger>
|
||||
<Button
|
||||
size="icon-sm"
|
||||
variant="outline"
|
||||
onclick={() => goToContent(prevNextContent.next)}
|
||||
disabled={isNextDisabled}
|
||||
aria-label={$t('course.navItem.lessons.next')}
|
||||
>
|
||||
<ChevronRightIcon size={14} />
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="bottom" sideOffset={4}>
|
||||
{$t('course.navItem.lessons.next_shortcut')}
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
</Tooltip.Provider>
|
||||
</div>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import type { TLocale } from '@cio/db/types';
|
||||
import AIButton from '$features/course/components/lesson/ai-button.svelte';
|
||||
import type { Writable } from 'svelte/store';
|
||||
import { saveDraft } from '$features/course/utils/lesson-draft';
|
||||
|
||||
interface Props {
|
||||
mode?: (typeof MODES)[keyof typeof MODES];
|
||||
@@ -33,12 +34,7 @@
|
||||
}
|
||||
lessonApi.translations[lessonId][lessonApi.currentLocale] = `${content}`;
|
||||
|
||||
try {
|
||||
localStorage.setItem(`lesson-${lessonId}-${lessonApi.currentLocale}`, `${content}`);
|
||||
} catch (error) {
|
||||
console.error('Error saving lesson note to localStorage', error);
|
||||
}
|
||||
|
||||
saveDraft(lessonId, lessonApi.currentLocale, `${content}`);
|
||||
lessonApi.isDirty = true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user