feat: add 4 certificate templates (#295)

* add cloud translation api package

* feat: Script that auto generates translations for other languages

* add comments

* fix: broken translation

* improve translation

* update locck file

* clean up

* all round trasnlations check

* checkpoint

* finish up

* move to dev dependencies

* fix vercel build error

* translations fix

* - add documentation
- translation bug fix

* quick adjust

* PR Fixes

* add new line

* update docs

* checkpoint

* feat: redesign & addition of certificates

* text color update

* patterned width fix

* "student-name" class remove

* checkpoint

* PR Fix

* quick fix

* PR Fixes

* update certificate ready page for students

* PR Fixes

* quick cleanup

* move fetchCourses into .ts file

* feat: give students access to certificates only after completing course

* checkpoint

* height fix

* Merge branch 'main' of https://github.com/rotimi-best/classroomio into classroomio-issue-272

* Fix; merge PR Fix

* fix: width of plain & professional certs

* PR Fix

* feat: add translation

* handle error when trying to download a certificate

* add fetchExploreCourses

* fix: requested reviews

* review fixes checkpoint

* replace the certificate background

* height fix for patterned certififcates

* fix more bugs

* rename schema

---------

Co-authored-by: Rotimi Best <best@classroomio.com>
This commit is contained in:
Adeyemi Adetunji
2024-07-21 03:02:33 +01:00
committed by GitHub
co-authored by Rotimi Best
parent f0907dde89
commit 72b58ed2c7
48 changed files with 2940 additions and 251 deletions
@@ -125,6 +125,17 @@ const onboardingValidationSchema = {
})
};
const saveCertificateSchema = z.object({
description: z.string().max(200, 'course.navItem.certificates.description_error'),
is_certificate_downloadable: z.boolean(),
certificate_theme: z.string()
});
export const saveCertificateValidation = (fields = {}) => {
const { error } = saveCertificateSchema.safeParse(fields);
return processErrors(error);
};
const updateProfileValidationSchema = z.object({
email: z.string().email({ message: 'validations.user_profile.email' }),
username: z.string().nonempty({ message: 'validations.user_profile.username' }),
@@ -1,3 +1,4 @@
import { get } from 'svelte/store';
import { supabase } from '$lib/utils/functions/supabase';
import { isUUID } from '$lib/utils/functions/isUUID';
import { QUESTION_TYPE } from '$lib/components/Question/constants';
@@ -11,7 +12,53 @@ import type {
ExerciseTemplate
} from '$lib/utils/types';
import { STATUS } from '$lib/utils/constants/course';
import type { PostgrestSingleResponse } from '@supabase/supabase-js';
import type { PostgrestSingleResponse, PostgrestError } from '@supabase/supabase-js';
import type { ProfileCourseProgress } from '$lib/utils/types';
import { isOrgAdmin } from '$lib/utils/store/org';
export async function fetchCourses(profileId, orgId) {
if (!orgId || !profileId) return;
const match = {};
// Filter by profile_id if role isn't admin within organization
if (!get(isOrgAdmin)) {
match.member_profile_id = profileId;
}
// Gets courses for a particular organisation where the current logged in user is a groupmember
const { data: allCourses } = await supabase
.rpc('get_courses', {
org_id_arg: orgId,
profile_id_arg: profileId
})
.match(match);
console.log(`allCourses`, allCourses);
if (!Array.isArray(allCourses)) {
return {
allCourses: []
};
}
return { allCourses };
}
export async function fetchProfileCourseProgress(
courseId,
profileId
): Promise<{
data: ProfileCourseProgress[] | null;
error: PostgrestError | null;
}> {
const { data, error } = await supabase
.rpc('get_course_progress', {
course_id_arg: courseId,
profile_id_arg: profileId
})
.returns<ProfileCourseProgress[]>();
return { data, error };
}
const SLUG_QUERY = `
id,
@@ -95,6 +142,23 @@ export async function fetchCourse(courseId?: Course['id'], slug?: Course['slug']
};
}
export async function fetchExploreCourses(profileId, orgId) {
if (!orgId || !profileId) return;
const { data: allCourses } = await supabase.rpc('get_explore_courses', {
org_id_arg: orgId,
profile_id_arg: profileId
});
if (!Array.isArray(allCourses)) {
return {
allCourses: []
};
}
return { allCourses };
}
export async function fetchGroup(groupId: Group['id']) {
const { data, error } = await supabase
.from('group')
@@ -409,11 +409,17 @@
"locked": "Gesperrt",
"unlocked": "Entsperrt",
"save": "Änderungen speichern",
"unlocked_certificate": "Zertifikat ist jetzt verfügbar",
"locked_certificate": "Zertifikate nicht verfügbar",
"instructor_unlock": "Ihr Dozent muss Ihr Zertifikat entsperren.",
"contact_instructor": "Wenn Sie Ihr Zertifikat benötigen, wenden Sie sich bitte an Ihren Dozenten.",
"download_certificate": "Zertifikat herunterladen"
"download_certificate": "Zertifikat herunterladen",
"unlocked_certificate": "Glückwunsch! 🎉 Dein Zertifikat ist fertig",
"unlocked_certificate_subtitle": "Sie haben diesen Kurs abgeschlossen und Ihr Engagement ist offensichtlich. Laden Sie das Zertifikat herunter und teilen Sie es mit Ihrem Netzwerk als Beweis für Ihre neu erworbenen Fähigkeiten",
"complete_to_download_subtitle": "Ihr Zertifikat ist verfügbar, sobald Sie alle Lektionen und Übungen dieses Kurses abgeschlossen haben",
"complete_to_download_title": "Kurs nicht abgeschlossen 😔",
"description_error": "Die Beschreibung darf 200 Zeichen nicht überschreiten",
"characters": "Figuren",
"unexpected_error": "Ein unerwarteter Fehler ist aufgetreten"
},
"people": {
"title": "Menschen",
@@ -855,7 +855,7 @@
"theme": "Choose a theme",
"logo": "Brand Logo",
"to_update": "To update your brand image, go to",
"settings": "Settings &gt; Organisation settings",
"settings": "Settings > Organisation settings",
"and_upload": "and upload your brand logo",
"goto_settings": "Go to Settings",
"description": "Course Description",
@@ -864,11 +864,17 @@
"locked": "Locked",
"unlocked": "Unlocked",
"save": "Save Changes",
"unlocked_certificate": "Certificate is now available",
"unlocked_certificate": "Congrats! 🎉 your certificate is ready",
"unlocked_certificate_subtitle": "You have completed this course, and your dedication is evident. Download certificate and share with your network as a prove of your newly acquired skill",
"locked_certificate": "Certificates not available",
"instructor_unlock": "Your instructor needs to unlock your certificate.",
"contact_instructor": "If you need your certificate, please contact your instructor.",
"download_certificate": "DownLoad Certificate"
"download_certificate": "Download Certificate",
"complete_to_download_title": "Course not complete 😔",
"complete_to_download_subtitle": "Your certificate will be available once you complete all lesson and exercises in this course",
"description_error": "Description cannot exceed 200 characters",
"characters": "characters",
"unexpected_error": "An unexpected error occurred"
},
"landing_page": {
@@ -409,11 +409,17 @@
"locked": "bloqueado",
"unlocked": "desbloqueado",
"save": "Guardar cambios",
"unlocked_certificate": "El certificado ya está disponible",
"locked_certificate": "Certificados no disponibles",
"instructor_unlock": "Su instructor necesita desbloquear su certificado.",
"contact_instructor": "Si necesita su certificado, comuníquese con su instructor.",
"download_certificate": "Descargar certificado"
"download_certificate": "Descargar certificado",
"unlocked_certificate": "¡Felicitaciones! 🎉 tu certificado está listo",
"unlocked_certificate_subtitle": "Has completado este curso y tu dedicación es evidente. Descargue el certificado y compártalo con su red como prueba de su habilidad recién adquirida",
"complete_to_download_subtitle": "Su certificado estará disponible una vez que complete todas las lecciones y ejercicios de este curso.",
"complete_to_download_title": "Curso no completo 😔",
"description_error": "La descripción no puede exceder los 200 caracteres.",
"characters": "caracteres",
"unexpected_error": "ocurrió un error inesperado"
},
"people": {
"title": "Gente",
@@ -409,11 +409,17 @@
"locked": "Fermé à clé",
"unlocked": "Débloqué",
"save": "Sauvegarder les modifications",
"unlocked_certificate": "Le certificat est maintenant disponible",
"locked_certificate": "Certificats non disponibles",
"instructor_unlock": "Votre instructeur doit débloquer votre certificat.",
"contact_instructor": "Si vous avez besoin de votre certificat, veuillez contacter votre instructeur.",
"download_certificate": "Télécharger le certificat"
"download_certificate": "Télécharger le certificat",
"unlocked_certificate": "Bravo! 🎉 votre attestation est prête",
"unlocked_certificate_subtitle": "Vous avez terminé ce cours et votre dévouement est évident. Téléchargez le certificat et partagez-le avec votre réseau comme preuve de vos compétences nouvellement acquises",
"complete_to_download_subtitle": "Votre certificat sera disponible une fois que vous aurez terminé toutes les leçons et exercices de ce cours.",
"complete_to_download_title": "Cours non terminé 😔",
"description_error": "La description ne peut pas dépasser 200 caractères",
"characters": "personnages",
"unexpected_error": "une erreur inattendue est apparue"
},
"people": {
"title": "Personnes",
@@ -409,11 +409,17 @@
"locked": "बंद",
"unlocked": "अनलॉक किया",
"save": "परिवर्तनों को सुरक्षित करें",
"unlocked_certificate": "प्रमाणपत्र अब उपलब्ध है",
"locked_certificate": "प्रमाणपत्र उपलब्ध नहीं हैं",
"instructor_unlock": "आपके प्रशिक्षक को आपका प्रमाणपत्र अनलॉक करना होगा.",
"contact_instructor": "यदि आपको अपने प्रमाणपत्र की आवश्यकता है, तो कृपया अपने प्रशिक्षक से संपर्क करें।",
"download_certificate": "प्रमाणपत्र डाउनलोड करें"
"download_certificate": "प्रमाणपत्र डाउनलोड करें",
"unlocked_certificate_subtitle": "आपने यह पाठ्यक्रम पूरा कर लिया है, और आपका समर्पण स्पष्ट है। प्रमाणपत्र डाउनलोड करें और अपने नए अर्जित कौशल के प्रमाण के रूप में अपने नेटवर्क के साथ साझा करें",
"unlocked_certificate": "बधाई! 🎉आपका सर्टिफिकेट तैयार है",
"complete_to_download_subtitle": "इस पाठ्यक्रम के सभी पाठ और अभ्यास पूरा करने के बाद आपका प्रमाणपत्र उपलब्ध हो जाएगा",
"complete_to_download_title": "कोर्स पूरा नहीं हुआ 😔",
"description_error": "विवरण 200 वर्णों से अधिक नहीं हो सकता",
"characters": "पात्र",
"unexpected_error": "एक अप्रत्याशित त्रुटि हुई"
},
"people": {
"title": "लोग",
@@ -409,11 +409,17 @@
"locked": "Bloqueado",
"unlocked": "Desbloqueado",
"save": "Salvar alterações",
"unlocked_certificate": "Certificado já está disponível",
"locked_certificate": "Certificados não disponíveis",
"instructor_unlock": "Seu instrutor precisa desbloquear seu certificado.",
"contact_instructor": "Se precisar do seu certificado, entre em contato com seu instrutor.",
"download_certificate": "Certificado de download"
"download_certificate": "Certificado de download",
"unlocked_certificate": "Parabéns! 🎉 seu certificado está pronto",
"unlocked_certificate_subtitle": "Você concluiu este curso e sua dedicação é evidente. Baixe o certificado e compartilhe com sua rede como prova de sua habilidade recém-adquirida",
"complete_to_download_subtitle": "Seu certificado estará disponível assim que você concluir todas as lições e exercícios deste curso",
"complete_to_download_title": "Curso não concluído 😔",
"description_error": "A descrição não pode exceder 200 caracteres",
"characters": "personagens",
"unexpected_error": "Um erro inesperado ocorreu"
},
"people": {
"title": "Pessoas",
@@ -409,11 +409,17 @@
"locked": "Заблокировано",
"unlocked": "Разблокировано",
"save": "Сохранить изменения",
"unlocked_certificate": "Сертификат уже доступен",
"locked_certificate": "Сертификаты недоступны",
"instructor_unlock": "Преподавателю необходимо разблокировать ваш сертификат.",
"contact_instructor": "Если вам нужен сертификат, обратитесь к преподавателю.",
"download_certificate": "Скачать сертификат"
"download_certificate": "Скачать сертификат",
"unlocked_certificate": "Поздравляю! 🎉ваш сертификат готов",
"unlocked_certificate_subtitle": "Вы прошли этот курс, и ваша преданность делу очевидна. Загрузите сертификат и поделитесь им со своей сетью в качестве доказательства вашего недавно приобретенного навыка.",
"complete_to_download_subtitle": "Ваш сертификат будет доступен после завершения всех уроков и упражнений этого курса.",
"complete_to_download_title": "Курс не завершен 😔",
"description_error": "Описание не может превышать 200 символов.",
"characters": "персонажи",
"unexpected_error": "произошла непредвиденная ошибка"
},
"people": {
"title": "Люди",
@@ -409,11 +409,17 @@
"locked": "Đã khóa",
"unlocked": "Đã mở khóa",
"save": "Lưu thay đổi",
"unlocked_certificate": "Chứng chỉ hiện có sẵn",
"locked_certificate": "Chứng chỉ không có sẵn",
"instructor_unlock": "Người hướng dẫn của bạn cần mở khóa chứng chỉ của bạn.",
"contact_instructor": "Nếu bạn cần chứng chỉ của mình, vui lòng liên hệ với người hướng dẫn của bạn.",
"download_certificate": "Tải xuống chứng chỉ"
"download_certificate": "Tải xuống chứng chỉ",
"unlocked_certificate": "Chúc mừng! 🎉 chứng chỉ của bạn đã sẵn sàng",
"unlocked_certificate_subtitle": "Bạn đã hoàn thành khóa học này và sự cống hiến của bạn là điều hiển nhiên. Tải xuống chứng chỉ và chia sẻ với mạng của bạn như một bằng chứng về kỹ năng mới có được của bạn",
"complete_to_download_subtitle": "Chứng chỉ của bạn sẽ có sau khi bạn hoàn thành tất cả bài học và bài tập trong khóa học này",
"complete_to_download_title": "Khóa học chưa hoàn thành 😔",
"description_error": "Mô tả không thể vượt quá 200 ký tự",
"characters": "nhân vật",
"unexpected_error": "Đã xảy ra lỗi không mong muốn"
},
"people": {
"title": "Mọi người",
@@ -13,6 +13,14 @@ export enum LOCALE {
}
//===============Custom Type===============
export interface ProfileCourseProgress {
exercises_completed: number;
exercises_count: number;
lessons_completed: number;
lessons_count: number;
}
export interface GroupPerson {
assigned_student_id: number | null;
created_at: string;