feat: redo settings and all org pages. (#601)
* feat: new components, deploy storybook * refactor: simplify component field definitions and enhance button loading state - Removed unnecessary type imports and simplified the FIELDS array definitions across multiple components in Storybook. - Updated button component to use a loading state instead of isLoading, improving clarity and consistency in props. - Adjusted button stories to reflect the new loading prop and enhanced the button's loading visual feedback. - Minor CSS adjustments for better layout and styling consistency. * chore: add field, card, password components * feat: improve page component & update AuthUI path * chore: update bits-ui to version 2.14.4 and all org related UI's - Updated bits-ui dependency in pnpm-lock.yaml and package.json to version 2.14.4. - Refactored imports for RoleBasedSecurity and UpgradeBanner components to use the new ui feature directory. - Removed unused Course components and related files to clean up the codebase. - Enhanced various components to improve consistency and maintainability. * chore: move all setting buttons to the top and update auth ui styles * chore: remove svelte-loading-spinners and replace with @cio/ui/base/spinner - Removed all instances of svelte-loading-spinners from the project. - Updated components to use the new Spinner from @cio/ui/base/spinner. - Cleaned up pnpm-lock.yaml and package.json to reflect the removal of svelte-loading-spinners. - Added new CSS animations for rocket launch effects in app.css. - Enhanced email change flow with updated verification messages in translations. * chore: remove supabase file upload from dashboard and move to r2 * chore: update organization API and UI components - Added new API methods for organization plan operations and team management. - Introduced middleware for API key authentication and organization admin checks. - Refactored organization-related components to utilize the new API structure. - Updated various UI components to improve consistency and maintainability. - Enhanced error handling and validation for organization-related actions. - Removed deprecated services and streamlined the codebase.
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
import { BaseApiWithErrors, classroomio } from '$lib/utils/services/api';
|
||||
|
||||
export interface SetupItem {
|
||||
id: string;
|
||||
title: string;
|
||||
desc: string;
|
||||
is_completed: boolean;
|
||||
button_label: string;
|
||||
}
|
||||
|
||||
export interface SetupProgressData {
|
||||
setup: SetupItem[];
|
||||
courses: Array<{ id: string; [key: string]: unknown }>;
|
||||
lessons: Array<{ id: string; [key: string]: unknown }>;
|
||||
orgSiteName: string;
|
||||
}
|
||||
|
||||
type TCreateSetupData = {
|
||||
orgHasAvatarUrl?: boolean;
|
||||
isCourseCreated?: boolean;
|
||||
isLessonCreated?: boolean;
|
||||
isExerciseCreated?: boolean;
|
||||
isCoursePublished?: boolean;
|
||||
};
|
||||
|
||||
// Base setup data structure - created once
|
||||
const createSetupData = ({
|
||||
orgHasAvatarUrl = false,
|
||||
isCourseCreated = false,
|
||||
isLessonCreated = false,
|
||||
isExerciseCreated = false,
|
||||
isCoursePublished = false
|
||||
}: TCreateSetupData = {}): SetupItem[] => [
|
||||
{
|
||||
id: 'profile',
|
||||
title: 'setup.personal_profile.title',
|
||||
desc: 'setup.personal_profile.desc',
|
||||
is_completed: false,
|
||||
button_label: 'setup.personal_profile.button_label'
|
||||
},
|
||||
{
|
||||
id: 'organization',
|
||||
title: 'setup.organization_profile.title',
|
||||
desc: 'setup.organization_profile.desc',
|
||||
is_completed: orgHasAvatarUrl,
|
||||
button_label: 'setup.organization_profile.button_label'
|
||||
},
|
||||
{
|
||||
id: 'course',
|
||||
title: 'setup.course.title',
|
||||
desc: 'setup.course.desc',
|
||||
is_completed: isCourseCreated,
|
||||
button_label: 'setup.course.button_label'
|
||||
},
|
||||
{
|
||||
id: 'lesson',
|
||||
title: 'setup.lesson.title',
|
||||
desc: 'setup.lesson.desc',
|
||||
is_completed: isLessonCreated,
|
||||
button_label: 'setup.lesson.button_label'
|
||||
},
|
||||
{
|
||||
id: 'exercise',
|
||||
title: 'setup.exercise.title',
|
||||
desc: 'setup.exercise.desc',
|
||||
is_completed: isExerciseCreated,
|
||||
button_label: 'setup.exercise.button_label'
|
||||
},
|
||||
{
|
||||
id: 'publish',
|
||||
title: 'setup.publish_course.title',
|
||||
desc: 'setup.publish_course.desc',
|
||||
is_completed: isCoursePublished,
|
||||
button_label: 'setup.publish_course.button_label'
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* API class for setup progress operations
|
||||
*/
|
||||
class SetupProgressApi extends BaseApiWithErrors {
|
||||
progress = $state<SetupProgressData>({
|
||||
orgSiteName: '',
|
||||
setup: [],
|
||||
courses: [],
|
||||
lessons: []
|
||||
});
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.isLoading = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computed setup list with profile completion check
|
||||
* This computation is shared across components to avoid duplication
|
||||
* Note: Profile completion check should be done in components using $profile
|
||||
* This returns the base setup list, and components can derive the final list
|
||||
*/
|
||||
get setupList(): SetupItem[] {
|
||||
return this.progress.setup || [];
|
||||
}
|
||||
/**
|
||||
* Fetches setup progress data for an organization
|
||||
* @param siteName Organization site name
|
||||
*/
|
||||
async fetchSetupProgress(siteName: string) {
|
||||
await this.execute<typeof classroomio.organization.setup.$get>({
|
||||
requestFn: () =>
|
||||
classroomio.organization.setup.$get({
|
||||
query: { siteName }
|
||||
}),
|
||||
logContext: 'fetching setup progress',
|
||||
onSuccess: (response) => {
|
||||
if (!response.data) {
|
||||
// Set default empty state if no data
|
||||
const setup = createSetupData();
|
||||
this.progress = {
|
||||
orgSiteName: siteName,
|
||||
setup,
|
||||
courses: [],
|
||||
lessons: []
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
isCourseCreated,
|
||||
isCoursePublished,
|
||||
orgHasAvatarUrl,
|
||||
courseData,
|
||||
lessonData,
|
||||
isLessonCreated,
|
||||
isExerciseCreated
|
||||
} = response.data;
|
||||
|
||||
const setup = createSetupData({
|
||||
orgHasAvatarUrl,
|
||||
isCourseCreated,
|
||||
isLessonCreated,
|
||||
isExerciseCreated,
|
||||
isCoursePublished
|
||||
});
|
||||
|
||||
this.progress = {
|
||||
orgSiteName: siteName,
|
||||
setup,
|
||||
courses: courseData || [],
|
||||
lessons: lessonData || []
|
||||
};
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('Error fetching setup progress:', error);
|
||||
// Set default empty state on error
|
||||
const setup = createSetupData();
|
||||
this.progress = {
|
||||
orgSiteName: siteName,
|
||||
setup,
|
||||
courses: [],
|
||||
lessons: []
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const setupProgressApi = new SetupProgressApi();
|
||||
|
||||
/**
|
||||
* Calculates progress percentage from setup items
|
||||
*/
|
||||
export function calculateProgress(setupItems: SetupItem[]): number {
|
||||
if (!setupItems || setupItems.length === 0) return 0;
|
||||
const completed = setupItems.filter((item) => item.is_completed).length;
|
||||
return Math.round((completed / setupItems.length) * 100);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<script lang="ts">
|
||||
import { calculateProgress, setupProgressApi } from '../api/setup-progress.svelte';
|
||||
import type { SetupItem } from '../api/setup-progress.svelte';
|
||||
import { Skeleton } from '@cio/ui/base/skeleton';
|
||||
|
||||
let { setupItems, size = 'default' } = $props<{
|
||||
setupItems?: SetupItem[];
|
||||
size?: 'small' | 'default';
|
||||
}>();
|
||||
|
||||
const effectiveSetupItems = $derived(setupItems ?? setupProgressApi.progress.setup ?? []);
|
||||
const progressPercentage = $derived(calculateProgress(effectiveSetupItems));
|
||||
const isLoading = $derived(setupProgressApi.isLoading);
|
||||
const sizeClass = $derived(size === 'small' ? 'size-10' : 'size-14');
|
||||
</script>
|
||||
|
||||
<div class="relative shrink-0">
|
||||
{#if isLoading}
|
||||
<!-- Loading skeleton - round circle -->
|
||||
<Skeleton class="{sizeClass} rounded-full" />
|
||||
{:else}
|
||||
<svg class="{sizeClass} -rotate-90" viewBox="0 0 100 100">
|
||||
<!-- Background circle -->
|
||||
<circle
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="45"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="8"
|
||||
class="text-gray-200 dark:text-gray-700"
|
||||
/>
|
||||
<!-- Progress circle -->
|
||||
<circle
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="45"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="8"
|
||||
stroke-linecap="round"
|
||||
class="text-green-600"
|
||||
stroke-dasharray={2 * Math.PI * 45}
|
||||
stroke-dashoffset={2 * Math.PI * 45 * (1 - progressPercentage / 100)}
|
||||
style="transition: stroke-dashoffset 0.5s ease-in-out;"
|
||||
/>
|
||||
</svg>
|
||||
<!-- Center text -->
|
||||
<div class="absolute inset-0 flex flex-col items-center justify-center">
|
||||
<p class="{size === 'small' ? 'text-[10px]' : 'text-sm'} font-bold">{progressPercentage}%</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
Reference in New Issue
Block a user