feat: 🚀 😃 convert repo into a monorepo 🚀 😃 (#51)

* Merge main into turbo-monrepo

* feat: rename apps

* feat: remove supbase videos and serve all from project

* feat: move supabase to root

* feat: update README

* docs: update link
This commit is contained in:
Rotimi Best
2023-11-25 21:56:02 +00:00
committed by GitHub
parent 03ca379094
commit 41e29014ef
806 changed files with 85853 additions and 5746 deletions
@@ -0,0 +1,8 @@
export async function handleAuthChange(event, session) {
await fetch('/api/auth', {
method: 'POST',
headers: new Headers({ 'Content-Type': 'application/json' }),
credentials: 'same-origin',
body: JSON.stringify({ event, session })
});
}
@@ -0,0 +1,29 @@
export function toggleBodyByMode(isDark) {
if (!document) return;
if (isDark) {
document.body.className = document.body.className.concat(' ', 'dark');
} else {
document.body.className = document.body.className.replace(' dark', '');
}
}
export function isCoursesPage(path) {
return /courses\/[a-z 0-9 -]/.test(path);
}
export function isCoursePage(path) {
return /course\/[a-z 0-9 -]/.test(path);
}
export function isOrgPage(path) {
return /org\/[a-z 0-9 -]/.test(path);
}
export function isQuizPage(path) {
return /org\/[a-z 0-9 -]+\/quiz\/[a-z 0-9 -]/.test(path);
}
export function isLMSPage(path) {
return /lms[\/a-z 0-9 -]*/.test(path);
}
@@ -0,0 +1,41 @@
import { browser } from '$app/environment';
import type { Course } from '../types';
export const isCourseFree = (cost: number) => !(Number(cost) > 0);
export const getStudentInviteLink = (_course: Course, orgSiteName: string) => {
const hash = encodeURIComponent(
btoa(
JSON.stringify({
groupId: _course.group?.id,
name: _course.title,
description: _course.description,
orgSiteName
})
)
);
return `https://${orgSiteName}.classroomio.com/invite/s/${hash}`;
};
export const getTextFromHTML = (html: string): string => {
if (!browser) return html;
const dummyDiv = document.createElement('div');
dummyDiv.innerHTML = html;
return dummyDiv.textContent?.trim() || '';
};
const tagsToReplace = {
'&': '&',
'<': '&lt;',
'>': '&gt;'
};
export function replaceHTMLTag(text: string) {
return text
.split('')
.map((char) => tagsToReplace[char] || char)
.join('');
}
@@ -0,0 +1,14 @@
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
dayjs.extend(relativeTime);
export const calDateDiff = (date: string | number | Date): string => {
return dayjs(date).fromNow(true) + ` ago`;
};
export const getGreeting = () => {
const date = new Date();
const hours = date.getHours();
return hours < 12 ? 'Good Morning' : hours < 18 ? 'Good Afternoon' : 'Good Evening';
};
@@ -0,0 +1,13 @@
export default (date) => {
const d = new Date(date);
const ye = new Intl.DateTimeFormat('en', { year: 'numeric' }).format(d);
const mo = new Intl.DateTimeFormat('en', { month: '2-digit' }).format(d);
const da = new Intl.DateTimeFormat('en', { day: '2-digit' }).format(d);
// return `${ye}-${mo}-${da} ${d.toLocaleTimeString('en-US')}`;
return new Intl.DateTimeFormat('en-US', {
dateStyle: 'full',
timeStyle: 'medium'
}).format(d);
};
@@ -0,0 +1,51 @@
export function formatYoutubeVideo(url: string, errors: { video: string }) {
console.log(`url`, url);
const prefix = 'https://www.youtube.com/embed';
// https://www.youtube.com/embed/qajK1J1neAM
if (url.includes('embed')) {
return url;
}
// https://youtu.be/qajK1J1neAM
if (url.includes('.be/')) {
const splittedUrlWithBe = url.split('.be/');
return `${prefix}/${splittedUrlWithBe[1]}`;
}
// https://www.youtube.com/watch?v=qajK1J1neAM
const splitedUrl = url.split('watch');
if (splitedUrl.length !== 2) {
errors.video = 'Not a valid youtube link';
return;
}
const query = new URLSearchParams(splitedUrl[1]);
return `${prefix}/${query.get('v')}`;
}
export function getEmbedId(url: string) {
// https://www.youtube.com/embed/qajK1J1neAM
if (url.includes('embed')) {
return url.split('embed/')[1];
}
// https://youtu.be/qajK1J1neAM
if (url.includes('.be/')) {
const splittedUrlWithBe = url.split('.be/');
return splittedUrlWithBe[1];
}
// https://www.youtube.com/watch?v=qajK1J1neAM
const splitedUrl = url.split('watch');
if (splitedUrl.length !== 2) {
return '';
}
const query = new URLSearchParams(splitedUrl[1]);
return query.get('v');
}
@@ -0,0 +1 @@
export default () => Date.now().toString(36) + Math.random().toString(36).substr(2);
@@ -0,0 +1,10 @@
function generateSlug(title) {
return title
.match(/[^\W\s\d]*/g, '')
.filter((v = '') => !!v.trim())
.join('-')
.toLowerCase()
.concat(`-${new Date().getTime()}`);
}
export default generateSlug;
@@ -0,0 +1,18 @@
export default function generateUUID() {
// Public Domain/MIT
var d = new Date().getTime(); //Timestamp
var d2 = (typeof performance !== 'undefined' && performance.now && performance.now() * 1000) || 0; //Time in microseconds since page-load or 0 if unsupported
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16; //random number between 0 and 16
if (d > 0) {
//Use timestamp until depleted
r = (d + r) % 16 | 0;
d = Math.floor(d / 16);
} else {
//Use microseconds since page-load if supported
r = (d2 + r) % 16 | 0;
d2 = Math.floor(d2 / 16);
}
return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);
});
}
@@ -0,0 +1,8 @@
export default (currency: string | undefined) => {
const locale = currency == 'NGN' ? 'en-NG' : 'en-US';
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: currency,
minimumFractionDigits: 2
});
};
@@ -0,0 +1,3 @@
export function isObject(obj) {
return obj === Object(obj);
}
@@ -0,0 +1,3 @@
export default (created_at, due_date) => {
return !!due_date ? !!(new Date(created_at).getTime() <= new Date(due_date).getTime()) : true;
};
@@ -0,0 +1,13 @@
export function isUUID(uuid: string) {
let s = '' + uuid;
const m: RegExpMatchArray | null = s.match(
'^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
);
if (m === null) {
return false;
}
return true;
}
@@ -0,0 +1,12 @@
export function genQuizPin(): number {
var minm = 100000;
var maxm = 999999;
return Math.floor(Math.random() * (maxm - minm + 1)) + minm;
}
export function generateSitename(orgName: string): string {
return orgName
.toLowerCase()
.replace(/\s+/g, '-')
.replace(/[^a-zA-Z0-9-]/g, '');
}
@@ -0,0 +1,5 @@
export function removeDuplicate(arr) {
let s = new Set(arr);
let it = s.values();
return Array.from(it);
}
@@ -0,0 +1,54 @@
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 '$lib/utils/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');
};
@@ -0,0 +1,8 @@
import { ROUTES_TO_HIDE_NAV } from '$lib/utils/constants/routes';
export default (route: string): boolean => {
return ROUTES_TO_HIDE_NAV.some((publicRoute) => {
const regex = new RegExp(publicRoute, 'g');
return regex.test(route);
});
};
@@ -0,0 +1,8 @@
import { PUBLIC_ROUTES } from '$lib/utils/constants/routes';
export default (route = '') => {
return PUBLIC_ROUTES.some((publicRoute) => {
const regex = new RegExp(publicRoute, 'g');
return regex.test(route);
});
};
@@ -0,0 +1,3 @@
export default (path: string): boolean => {
return ['login', 'signup', 'onboarding'].some((r) => path.includes(r)) || path === '';
};
@@ -0,0 +1,5 @@
function showAppsSideBar(path) {
return path.includes('/lessons/') && !path.includes('landingpage');
}
export default showAppsSideBar;
@@ -0,0 +1,3 @@
export function capitalizeFirstLetter(val: string): string {
return val.charAt(0).toUpperCase() + val.slice(1);
}
@@ -0,0 +1,24 @@
import {
PUBLIC_SUPABASE_ANON_KEY,
PUBLIC_SUPABASE_URL,
PUBLIC_SUPABASE_BUCKET_URL
} from '$env/static/public';
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;
config = {
supabaseConfig: {
bucketPath: PUBLIC_SUPABASE_BUCKET_URL + '/storage/v1/object/sign/',
url: PUBLIC_SUPABASE_URL || '',
anonKey: PUBLIC_SUPABASE_ANON_KEY || ''
}
};
supabase = createClient(config.supabaseConfig.url, config.supabaseConfig.anonKey);
return supabase;
};
@@ -0,0 +1,14 @@
export function setTheme(theme: string) {
// In case the default theme is added but another theme exists
if (!theme && document.body.className.includes('theme-')) {
const regex = /theme-[\w]+/gi;
document.body.className = document.body.className.replace(regex, theme);
return;
}
// In case theme already exists in dom, don't add
if (document.body.className.includes(theme)) return;
localStorage.setItem('theme', theme);
document.body.className = document.body.className.concat(' ', theme);
}
@@ -0,0 +1,8 @@
import { supabase } from '$lib/utils/functions/supabase';
import type { Profile } from '$lib/components/Course/components/People/types';
export async function getProfile(email: string): Promise<Profile | null> {
const { data } = await supabase.from('profile').select('*').eq('email', email).single();
console.log('getProfile data', data);
return data;
}
@@ -0,0 +1,5 @@
export function validateEmail(email) {
const re =
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(String(email).toLowerCase());
}
@@ -0,0 +1,259 @@
import isNumber from 'lodash/isNumber';
import z from 'zod';
import { validateEmail } from './validateEmail';
const createQuizValidationSchema = z.object({
title: z.string().min(6, {
message: 'Must be 6 or more characters long',
invalid_type_error: 'Must not be empty'
})
});
const askCommunityValidationSchema = z.object({
title: z.string().min(6, {
message: 'Must be 6 or more characters long',
invalid_type_error: 'Must not be empty'
}),
body: z.string().min(20, {
message: 'Must be 10 or more characters long',
invalid_type_error: 'Must not be empty'
})
});
const commentInCommunityValidationSchema = z.object({
comment: z.string().min(6, {
message: 'Must be 6 or more characters long',
invalid_type_error: 'Must not be empty'
})
});
const orgLandingpageValidationSchema = z.object({
name: z.string().min(6, {
message: 'Must be 6 or more characters long',
invalid_type_error: 'Must not be empty'
}),
email: z.string().email({
message: 'Invalid email address',
invalid_type_error: 'Must not be empty'
}),
phone: z.string().min(6, {
message: 'Must be 6 or more characters long',
invalid_type_error: 'Must not be empty'
}),
message: z.string().min(20, {
message: 'Must be 10 or more characters long',
invalid_type_error: 'Must not be empty'
})
});
const forgotValidationSchema = z.object({
email: z.string().email({
message: 'Invalid email address',
invalid_type_error: 'Must not be empty'
})
});
const authValidationSchema = z.object({
email: z.string().email({
message: 'Invalid email address',
invalid_type_error: 'Must not be empty'
}),
password: z.string().min(6, {
message: 'Must be 6 or more characters long',
invalid_type_error: 'Must not be empty'
})
});
const resetValidationSchema = z.object({
password: z.string().min(6, {
message: 'Must be 6 or more characters long',
invalid_type_error: 'Must not be empty'
})
});
const onboardingValidationSchema = {
stepOne: z.object({
fullname: z.string().min(1, { message: 'Enter your fullname' }),
orgName: z.string().min(1, { message: 'Enter your organisation name' }),
siteName: z.string().min(1, { message: 'Enter your site name' })
}),
stepTwo: z.object({
goal: z
.string({
required_error: 'Select an option',
invalid_type_error: 'Select an option'
})
.min(1),
source: z
.string({
required_error: 'Select an option',
invalid_type_error: 'Select an option'
})
.min(1)
})
};
export const getConfirmPasswordError = ({ password, confirmPassword }) => {
return password > 6 && confirmPassword > 6 && password !== confirmPassword
? 'Does not match password'
: undefined;
};
export const processErrors = (error, mapToId) => {
const errors = {};
if (Array.isArray(error?.issues)) {
for (const issue of error.issues) {
const { message, path } = issue;
if (!path.length) {
continue;
}
if (mapToId) {
let value = '';
path.forEach((p, i) => {
const isLast = !path[i + 1];
const formatP = isNumber(p) ? `[${p}]` : p;
value += !value ? formatP : `.${formatP}`;
});
const key = value.replace('label', 'id');
errors[key] = value;
} else {
errors[path[0]] = message;
}
}
}
return errors;
};
export const authValidation = (fields = {}) => {
const { error } = authValidationSchema.safeParse(fields);
return processErrors(error);
};
export const coursePaymentValidation = (fields = {}) => {
const schema = z.object({
fullname: z.string().min(6, {
message: 'Must be 6 or more characters long',
invalid_type_error: 'Must not be empty'
}),
email: z.string().email({
message: 'Invalid email address',
invalid_type_error: 'Must not be empty'
})
});
const { error } = schema.safeParse(fields);
return processErrors(error);
};
export const resetValidation = (fields = {}) => {
const { error } = resetValidationSchema.safeParse(fields);
return processErrors(error);
};
export const forgotValidation = (fields = {}) => {
const { error } = forgotValidationSchema.safeParse(fields);
return processErrors(error);
};
export const orgLandingpageValidation = (fields = {}) => {
const { error } = orgLandingpageValidationSchema.safeParse(fields);
return processErrors(error);
};
export const onboardingValidation = (fields = {}, step) => {
const schema =
step === 1 ? onboardingValidationSchema.stepOne : onboardingValidationSchema.stepTwo;
const { error } = schema.safeParse(fields);
return processErrors(error);
};
// export const createTemplateExerciseValidation = (fields = {}) => {
// const schema = z.object({
// orgName: z.string().min(5, {
// message: 'Must be 5 or more characters long',
// }),
// siteName: z.string().min(5, {
// message: 'Must be 5 or more characters long',
// })
// });
// const { error } = schema.safeParse(fields);
// return processErrors(error);
// };
export const createOrgValidation = (fields = {}) => {
const schema = z.object({
orgName: z.string().min(5, {
message: 'Must be 5 or more characters long',
invalid_type_error: 'Must not be empty'
}),
siteName: z.string().min(5, {
message: 'Must be 5 or more characters long',
invalid_type_error: 'Must not be empty'
})
});
const { error } = schema.safeParse(fields);
return processErrors(error);
};
export const createQuizValidation = (fields = {}) => {
const { error } = createQuizValidationSchema.safeParse(fields);
return processErrors(error);
};
export const askCommunityValidation = (fields = {}) => {
const { error } = askCommunityValidationSchema.safeParse(fields);
return processErrors(error);
};
export const commentInCommunityValidation = (fields = {}) => {
const { error } = commentInCommunityValidationSchema.safeParse(fields);
return processErrors(error);
};
export const getDisableSubmit = ({ password, confirmPassword }) => {
return !!(password && confirmPassword && password !== confirmPassword);
};
export const validateEmailInString = (emailsStr) => {
let error = '';
let hasError = false;
if (!emailsStr) {
return {
hasError: true,
error: 'Enter an email',
emails: []
};
}
const emails = emailsStr.split(',').map((_email) => {
const email = _email.trim();
if (!validateEmail(email)) {
hasError = true;
error += !error.length ? `Not valid email: ${email}` : `, ${email}`;
}
return email;
});
return {
hasError,
error,
emails
};
};