diff --git a/package-lock.json b/package-lock.json index 8e3adab6..aa9e9a77 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2312,6 +2312,12 @@ "assert-plus": "^1.0.0" } }, + "dayjs": { + "version": "1.10.7", + "resolved": "https://flipplib.jfrog.io/artifactory/api/npm/flipp-npm/dayjs/-/dayjs-1.10.7.tgz", + "integrity": "sha1-LPX5Gt0oEWdIRAhmoKHSbzps5Gg=", + "dev": true + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -3254,6 +3260,12 @@ "verror": "1.10.0" } }, + "just-throttle": { + "version": "2.3.1", + "resolved": "https://flipplib.jfrog.io/artifactory/api/npm/flipp-npm/just-throttle/-/just-throttle-2.3.1.tgz", + "integrity": "sha1-70oHEAc7tLD699rRvEzKuu7E0OY=", + "dev": true + }, "lie": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", @@ -3834,15 +3846,6 @@ "pinkie": "^2.0.0" } }, - "polka": { - "version": "1.0.0-next.22", - "resolved": "https://registry.npmjs.org/polka/-/polka-1.0.0-next.22.tgz", - "integrity": "sha512-a7tsZy5gFbJr0aUltZS97xCkbPglXuD67AMvTyZX7BTDBH384FWf0ZQF6rPvdutSxnO1vUlXM2zSLf5tCKk5RA==", - "requires": { - "@polka/url": "^1.0.0-next.21", - "trouter": "^3.1.0" - } - }, "postcss": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.0.tgz", @@ -4163,11 +4166,6 @@ "@babel/runtime": "^7.8.4" } }, - "regexparam": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/regexparam/-/regexparam-1.3.0.tgz", - "integrity": "sha512-6IQpFBv6e5vz1QAqI+V4k8P2e/3gRrqfCJ9FI+O1FLQTO+Uz6RXZEZOPmTJ6hlGj7gkERzY5BRCv09whKP96/g==" - }, "regexpu-core": { "version": "4.7.1", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.1.tgz", @@ -4712,6 +4710,16 @@ "svelte": "^3.15.0" } }, + "svelte-calendar": { + "version": "3.1.6", + "resolved": "https://flipplib.jfrog.io/artifactory/api/npm/flipp-npm/svelte-calendar/-/svelte-calendar-3.1.6.tgz", + "integrity": "sha1-vb+Unxnq4ZnQSGDaeFertQAOhEQ=", + "dev": true, + "requires": { + "dayjs": "^1.10.6", + "just-throttle": "^2.3.1" + } + }, "svelte-dnd-action": { "version": "0.9.9", "resolved": "https://registry.npmjs.org/svelte-dnd-action/-/svelte-dnd-action-0.9.9.tgz", @@ -4921,14 +4929,6 @@ "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=" }, - "trouter": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/trouter/-/trouter-3.2.0.tgz", - "integrity": "sha512-rLLXbhTObLy2MBVjLC+jTnoIKw99n0GuJs9ov10J870vDw5qhTurPzsDrudNtBf5w/CZ9ctZy2p2IMmhGcel2w==", - "requires": { - "regexparam": "^1.3.0" - } - }, "true-case-path": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz", diff --git a/package.json b/package.json index fa52a625..9c2e2c28 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "sass": "^1.32.13", "svelte": "^3.17.3", "svelte-awesome": "^2.3.1", + "svelte-calendar": "^3.1.6", "svelte-dnd-action": "^0.9.9", "svelte-loading-spinners": "^0.1.7", "svelte-preprocess": "^4.7.3", diff --git a/rollup.config.js b/rollup.config.js index 15e4d9e7..882b9b3b 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -14,7 +14,8 @@ import pkg from './package.json'; const mode = process.env.NODE_ENV; const dev = mode === 'development'; -const sourcemap = dev ? false : 'inline'; +// const sourcemap = dev ? false : 'inline'; +const sourcemap = 'inline'; const legacy = !!process.env.SAPPER_LEGACY_BUILD; const onwarn = (warning, onwarn) => { diff --git a/src/client.js b/src/client.js index c39b2e5f..09e27ae6 100644 --- a/src/client.js +++ b/src/client.js @@ -6,20 +6,22 @@ import config from './config'; console.log(`isProd`, config.isProd); -Sentry.init({ - dsn: 'https://c966f7e8cb1d4306be20b26bb4f0cc96@o476906.ingest.sentry.io/5999999', - integrations: [ - new Integrations.BrowserTracing(), - new CaptureConsole({ - levels: ['error'], - }), - ], - environment: config.isProd ? 'production' : 'development', - // Set tracesSampleRate to 1.0 to capture 100% - // of transactions for performance monitoring. - // We recommend adjusting this value in production - tracesSampleRate: 1.0, -}); +if (config.isProd) { + Sentry.init({ + dsn: 'https://c966f7e8cb1d4306be20b26bb4f0cc96@o476906.ingest.sentry.io/5999999', + integrations: [ + new Integrations.BrowserTracing(), + new CaptureConsole({ + levels: ['error'], + }), + ], + environment: config.isProd ? 'production' : 'development', + // Set tracesSampleRate to 1.0 to capture 100% + // of transactions for performance monitoring. + // We recommend adjusting this value in production + tracesSampleRate: 1.0, + }); +} sapper.start({ target: document.querySelector('#sapper'), diff --git a/src/components/Navigation/index.svelte b/src/components/Navigation/index.svelte index 8e934f52..10329014 100644 --- a/src/components/Navigation/index.svelte +++ b/src/components/Navigation/index.svelte @@ -28,7 +28,7 @@ {#if $user.isLoggedIn} -
  • + export async function preload(page, { user, config }) { + return { user, config }; + } + --> + + + + Admin Dashboard - ClassroomIO + + +
    +
    + {#each boxes as box} +
    + {#if box.icon === 'students'} + + {:else if box.icon === 'tutor'} + + {:else if box.icon === 'courses'} + + {/if} +

    {box.label}

    +

    {box.value}

    +
    + {/each} +
    + +
    +
    +
    +

    Outstanding students

    +
    + + {#each topStudents as topStudent, index} +
    + {index + 1} +

    {topStudent.name}

    + +

    {topStudent.score}

    +
    + {/each} +
    + +
    +
    +

    Top rated courses

    +
    + + {#each topCourses as topCourse, index} +
    + {index + 1} +

    {topCourse.title}

    + +

    {topCourse.rating}

    +
    + {/each} +
    +
    +
    + + diff --git a/src/routes/courses.svelte b/src/routes/courses.svelte index cee4cf0d..907af12a 100644 --- a/src/routes/courses.svelte +++ b/src/routes/courses.svelte @@ -130,7 +130,7 @@ - Courses + Courses - ClassroomIO
    diff --git a/src/routes/dashboard.svelte b/src/routes/dashboard.svelte index 27d44560..899acb3c 100644 --- a/src/routes/dashboard.svelte +++ b/src/routes/dashboard.svelte @@ -1,151 +1,259 @@ - - Dashboard - ClassroomIO -
    -
    - {#each boxes as box} -
    - {#if box.icon === 'students'} - - {:else if box.icon === 'tutor'} - - {:else if box.icon === 'courses'} - - {/if} -

    {box.label}

    -

    {box.value}

    +
    +
    + {#if !$isMobile} +
    +
    - {/each} -
    - -
    diff --git a/src/utils/functions/routes/dashboard.ts b/src/utils/functions/routes/dashboard.ts new file mode 100644 index 00000000..904fc9ae --- /dev/null +++ b/src/utils/functions/routes/dashboard.ts @@ -0,0 +1,56 @@ +import dayjs from 'dayjs'; +import isYesterday from 'dayjs/plugin/isYesterday'; +import isToday from 'dayjs/plugin/isToday'; +import isTomorrow from 'dayjs/plugin/isTomorrow'; + +import type { UserLessonDataType, LessonsByMonthIndexType } from '../../types'; + +dayjs.extend(isYesterday); +dayjs.extend(isToday); +dayjs.extend(isTomorrow); + +export const formatUserUpcomingData = ( + data: UserLessonDataType[] +): LessonsByMonthIndexType => { + const result: LessonsByMonthIndexType = {}; + + for (const item of data) { + const { lesson_at } = item; + const lessonAtDate = new Date(lesson_at); + const monthIndex = lessonAtDate.getMonth(); + const dateIndex = lessonAtDate.getDate(); + + if (result[monthIndex]) { + const prevDateData = result[monthIndex][dateIndex]; + + if (Array.isArray(prevDateData)) { + result[monthIndex] = { + ...result[monthIndex], + [dateIndex]: [...prevDateData, item], + }; + } else { + result[monthIndex] = { ...result[monthIndex], [dateIndex]: [item] }; + } + } else { + result[monthIndex] = { [dateIndex]: [item] }; + } + } + + return result; +}; + +export const formatDate = (_selectedDate: string | undefined): string => { + if (!_selectedDate) return ''; + + const selectedDate = dayjs(_selectedDate); + + if (selectedDate.isToday()) { + return 'Today'; + } else if (selectedDate.isYesterday()) { + return 'Yesterday'; + } else if (selectedDate.isTomorrow()) { + return 'Tomorrow'; + } + + return selectedDate.format('ddd, MMM D'); +}; diff --git a/src/utils/functions/supabase.js b/src/utils/functions/supabase.js deleted file mode 100644 index 00d23c0f..00000000 --- a/src/utils/functions/supabase.js +++ /dev/null @@ -1,24 +0,0 @@ -import { createClient } from '@supabase/supabase-js'; - -export let supabase; - -export const getSupabase = (config) => { - if (supabase) return supabase; - - if (!config && process && process.env) { - config = { - isProd: process.env.NODE_ENV === 'development', - supabaseConfig: { - url: process.env.SVELTE_APP_SUPABASE_URL, - anonKey: process.env.SVELTE_APP_SUPABASE_ANON_KEY, - }, - }; - } - - supabase = createClient( - config.supabaseConfig.url, - config.supabaseConfig.anonKey - ); - - return supabase; -}; diff --git a/src/utils/functions/supabase.ts b/src/utils/functions/supabase.ts new file mode 100644 index 00000000..c3278142 --- /dev/null +++ b/src/utils/functions/supabase.ts @@ -0,0 +1,25 @@ +import { createClient, SupabaseClient } from '@supabase/supabase-js'; +import type { ConfigType } from '../types'; + +export let supabase: SupabaseClient; + +export const getSupabase = (config: ConfigType) => { + if (supabase) return supabase; + + if (!config && process && process.env) { + config = { + isProd: process.env.NODE_ENV === 'development', + supabaseConfig: { + url: process.env.SVELTE_APP_SUPABASE_URL || '', + anonKey: process.env.SVELTE_APP_SUPABASE_ANON_KEY || '', + }, + }; + } + + supabase = createClient( + config.supabaseConfig.url, + config.supabaseConfig.anonKey + ); + + return supabase; +}; diff --git a/src/utils/services/dashboard/index.ts b/src/utils/services/dashboard/index.ts new file mode 100644 index 00000000..65b95f21 --- /dev/null +++ b/src/utils/services/dashboard/index.ts @@ -0,0 +1,25 @@ +import type { UserLessonDataType } from '../../types'; +import { supabase } from '../../functions/supabase'; + +export async function fetchUserUpcomingData( + profileId: string | null +): Promise { + if (!profileId) { + return []; + } + + const { data: userUpcomingData } = await supabase.rpc( + 'get_user_upcoming_lessons', + { + profile_id_arg: profileId, + } + ); + + console.log(`userUpcomingData`, userUpcomingData); + + if (!Array.isArray(userUpcomingData)) { + return []; + } + + return userUpcomingData; +} diff --git a/src/utils/types/config.ts b/src/utils/types/config.ts new file mode 100644 index 00000000..afec539d --- /dev/null +++ b/src/utils/types/config.ts @@ -0,0 +1,4 @@ +export type ConfigType = { + isProd: boolean; + supabaseConfig: { url: string; anonKey: string }; +}; diff --git a/src/utils/types/dashboard.ts b/src/utils/types/dashboard.ts new file mode 100644 index 00000000..25e7bab6 --- /dev/null +++ b/src/utils/types/dashboard.ts @@ -0,0 +1,16 @@ +export type UserLessonDataType = { + call_url: string; + course_id: string; + course_title: string; + lesson_at: string; + lesson_id: string; + lesson_title: string; +}; + +type LessonByDayType = { + [dayIndex: number]: UserLessonDataType[]; +}; + +export type LessonsByMonthIndexType = { + [monthIndex: number]: LessonByDayType; +}; diff --git a/src/utils/types/index.ts b/src/utils/types/index.ts new file mode 100644 index 00000000..e5959572 --- /dev/null +++ b/src/utils/types/index.ts @@ -0,0 +1,3 @@ +export * from './config'; +export * from './dashboard'; +export * from './user'; diff --git a/src/utils/types/user.ts b/src/utils/types/user.ts new file mode 100644 index 00000000..a8cf6750 --- /dev/null +++ b/src/utils/types/user.ts @@ -0,0 +1,17 @@ +export interface CurrentSessionType { + access_token: string; + expires_at: number; + expires_in: number; + refresh_token: string; + token_type: string; +} + +export interface UserType { + openAuthModal: boolean; + fetchingUser: boolean; + isLoggedIn: boolean; + currentSession: CurrentSessionType | null; + expiresAt: number; +} + +export interface ProfileType {}