feat: automatically detect language on server and translate UI (#530)

* feat: detect language on the server and translate page

* translate auth pages

* PR reviews
This commit is contained in:
Rotimi Best
2024-12-28 23:11:55 +01:00
committed by GitHub
parent 1f1ee8afd3
commit 381776f388
24 changed files with 431 additions and 232 deletions
+1 -1
View File
@@ -19,6 +19,6 @@
"deno.importMap": "./supabase/functions/import_map.json",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true
"source.organizeImports": "explicit"
}
}
@@ -1,11 +1,12 @@
<script lang="ts">
import { page } from '$app/stores';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
import GoogleIconColored from '../Icons/GoogleIconColored.svelte';
import Avatar from '$lib/components/Avatar/index.svelte';
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import { t } from '$lib/utils/functions/translations';
import { currentOrg } from '$lib/utils/store/org';
import type { SupabaseClient } from '@supabase/supabase-js';
import GoogleIconColored from '../Icons/GoogleIconColored.svelte';
export let supabase: SupabaseClient;
export let handleSubmit = () => {};
@@ -47,11 +48,11 @@
}
</script>
<div class="app-background w-full min-h-screen flex items-center justify-center">
<div class="container border border-gray bg-white dark:bg-black">
<div class="flex items-center flex-col p-2 lg:px-8 lg:py-3">
<div class="app-background flex min-h-screen w-full items-center justify-center">
<div class="border-gray container border bg-white dark:bg-black">
<div class="flex flex-col items-center p-2 lg:px-8 lg:py-3">
{#if !showOnlyContent || showLogo}
<div class="flex flex-col items-center justify-center w-full pt-2">
<div class="flex w-full flex-col items-center justify-center pt-2">
<Avatar
src={$currentOrg.avatar_url ? $currentOrg.avatar_url : '/logo-192.png'}
name={$currentOrg.name ? $currentOrg.name : 'ClassroomIO'}
@@ -61,7 +62,7 @@
className="mr-2"
/>
<a href="/">
<h4 class="dark:text-white text-xl mt-0">
<h4 class="mt-0 text-xl dark:text-white">
{$currentOrg.name ? $currentOrg.name : 'ClassroomIO'}
</h4>
</a>
@@ -70,13 +71,13 @@
<form
bind:this={formRef}
on:submit|preventDefault={handleSubmit}
class="flex items-center flex-col w-10/12"
class="flex w-10/12 flex-col items-center"
>
<slot />
</form>
{#if !showOnlyContent && !hideGoogleAuth}
<div class="w-10/12 mb-3">
<p class="dark:text-white text-sm mb-5">or sign up with:</p>
<div class="mb-3 w-10/12">
<p class="mb-5 text-sm dark:text-white">{$t('login.signup_with')}:</p>
<PrimaryButton
variant={VARIANTS.OUTLINED}
onClick={signInWithGoogle}
@@ -84,22 +85,24 @@
className="py-3 sm:w-full w-full"
>
<GoogleIconColored />
<span class="ml-2">{isLogin ? 'Sign in' : 'Sign up'} with Google</span>
<span class="ml-2">
{isLogin ? $t('login.login_with_google') : $t('login.signup_with_google')}
</span>
</PrimaryButton>
</div>
{/if}
</div>
{#if !showOnlyContent}
<div class="w-full p-6 border-t border-grey text-center">
<div class="border-grey w-full border-t p-6 text-center">
{#if isLogin}
Not registered yet? <a
class="hover:underline text-primary-700"
href="/signup{$page.url.search}">Sign up</a
{$t('login.not_registered_yet')}
<a class="text-primary-700 hover:underline" href="/signup{$page.url.search}"
>{$t('login.signup')}</a
>
{:else}
Already have an account? <a
class="hover:underline text-primary-700"
href="/login{$page.url.search}">Log In</a
{$t('login.already_have_account')}
<a class="text-primary-700 hover:underline" href="/login{$page.url.search}"
>{$t('login.login')}</a
>
{/if}
</div>
@@ -1,34 +1,34 @@
<script lang="ts">
import { fade } from 'svelte/transition';
import get from 'lodash/get';
import LogoFacebook from 'carbon-icons-svelte/lib/LogoFacebook.svelte';
import LogoTwitter from 'carbon-icons-svelte/lib/LogoTwitter.svelte';
import LogoLinkedin from 'carbon-icons-svelte/lib/LogoLinkedin.svelte';
import { Accordion, AccordionItem, Grid, Row, Column } from 'carbon-components-svelte';
import LocationFilled from 'carbon-icons-svelte/lib/LocationFilled.svelte';
import Phone from 'carbon-icons-svelte/lib/Phone.svelte';
import Email from 'carbon-icons-svelte/lib/Email.svelte';
import Rocket from 'carbon-icons-svelte/lib/Rocket.svelte';
import PageLoader from './PageLoader.svelte';
import { orgLandingpageValidation } from '$lib/utils/functions/validator';
import TextField from '$lib/components/Form/TextField.svelte';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
import Navigation from '$lib/components/Navigation/index.svelte';
import { goto } from '$app/navigation';
import Box from '$lib/components/Box/index.svelte';
import Card from '$lib/components/Courses/components/Card/index.svelte';
import CardLoader from '$lib/components/Courses/components/Card/Loader.svelte';
import CoursesEmptyIcon from '$lib/components/Icons/CoursesEmptyIcon.svelte';
import { getSupabase } from '$lib/utils/functions/supabase';
import { courses, courseMetaDeta } from '$lib/components/Courses/store';
import { getCourseBySiteName } from '$lib/utils/services/org';
import { validateEmail } from '$lib/utils/functions/validateEmail';
import { courseMetaDeta, courses } from '$lib/components/Courses/store';
import TextArea from '$lib/components/Form/TextArea.svelte';
import { goto } from '$app/navigation';
import TextField from '$lib/components/Form/TextField.svelte';
import CoursesEmptyIcon from '$lib/components/Icons/CoursesEmptyIcon.svelte';
import Navigation from '$lib/components/Navigation/index.svelte';
import { landingPageSettings } from '$lib/components/Org/Settings/store';
import { t } from '$lib/utils/functions/translations';
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import PoweredBy from '$lib/components/Upgrade/PoweredBy.svelte';
import { getSupabase } from '$lib/utils/functions/supabase';
import { t } from '$lib/utils/functions/translations';
import { validateEmail } from '$lib/utils/functions/validateEmail';
import { orgLandingpageValidation } from '$lib/utils/functions/validator';
import { getCourseBySiteName } from '$lib/utils/services/org';
import type { CurrentOrg } from '$lib/utils/types/org';
import { Accordion, AccordionItem, Column, Grid, Row } from 'carbon-components-svelte';
import Email from 'carbon-icons-svelte/lib/Email.svelte';
import LocationFilled from 'carbon-icons-svelte/lib/LocationFilled.svelte';
import LogoFacebook from 'carbon-icons-svelte/lib/LogoFacebook.svelte';
import LogoLinkedin from 'carbon-icons-svelte/lib/LogoLinkedin.svelte';
import LogoTwitter from 'carbon-icons-svelte/lib/LogoTwitter.svelte';
import Phone from 'carbon-icons-svelte/lib/Phone.svelte';
import Rocket from 'carbon-icons-svelte/lib/Rocket.svelte';
import get from 'lodash/get';
import { fade } from 'svelte/transition';
import PageLoader from './PageLoader.svelte';
export let orgSiteName = '';
export let org: CurrentOrg | null;
@@ -115,7 +115,6 @@
if (!siteName) return;
try {
console.log('sitename', siteName);
$courseMetaDeta.isLoading = true;
const coursesResult = await getCourseBySiteName(siteName);
courses.set(coursesResult);
@@ -182,7 +181,7 @@
{#if $landingPageSettings.header.show}
<header
id="header"
class={`w-full h-[100vh] md:h-[90vh] mb-10 relative ${
class={`relative mb-10 h-[100vh] w-full md:h-[90vh] ${
$landingPageSettings.header.background?.show
? 'bg-cover bg-center'
: 'border-b border-gray-300'
@@ -196,25 +195,25 @@
isOrgSite={true}
/>
<div class="absolute h-[100vh] md:h-[90vh] top-0 w-full opacity-80 z-10 bg-white" />
<div class="absolute top-0 z-10 h-[100vh] w-full bg-white opacity-80 md:h-[90vh]" />
{#if $landingPageSettings.header.banner.show}
<div class="flex items-center justify-center md:h-full py-2">
<div class="flex items-center justify-center py-2 md:h-full">
<div
class="md:w-11/12 lg:w-5/6 w-full flex items-center justify-center md:justify-between flex-col-reverse md:flex-row z-20 relative"
class="relative z-20 flex w-full flex-col-reverse items-center justify-center md:w-11/12 md:flex-row md:justify-between lg:w-5/6"
>
<!-- Course Description -->
<div class="md:w-2/5 w-11/12 py-10 flex flex-col items-center md:items-start mr-3">
<div class="mr-3 flex w-11/12 flex-col items-center py-10 md:w-2/5 md:items-start">
<p class=" text-primary-600 text-2xl font-semibold capitalize">
{org.name}
</p>
<h1
class="text-4xl md:text-5xl lg:text-6xl text-center md:text-start my-4 font-bold"
class="my-4 text-center text-4xl font-bold md:text-start md:text-5xl lg:text-6xl"
>
{$landingPageSettings.header.title} <br /><span class="text-primary-600"
>{$landingPageSettings.header.titleHighlight}</span
>
</h1>
<p class="text-md mb-3 md:mb-5 text-xl text-center md:text-start">
<p class="text-md mb-3 text-center text-xl md:mb-5 md:text-start">
{$landingPageSettings.header.subtitle}
</p>
@@ -228,7 +227,7 @@
/>
</div>
<div class="w-5/6 md:w-1/2 md:max-w-[650px] flex">
<div class="flex w-5/6 md:w-1/2 md:max-w-[650px]">
{#if isYouTubeLink($landingPageSettings.header?.banner?.video) && $landingPageSettings.header.banner.type === 'video'}
<div bind:this={player} id="player" style="width:100%; border-radius:12px">
<iframe
@@ -256,10 +255,10 @@
</div>
</div>
{:else}
<div class="w-full h-full flex items-center justify-center md:flex-row z-20 relative">
<div class="max-w-[600px] mx-auto w-11/12 py-10 flex flex-col items-center">
<div class="relative z-20 flex h-full w-full items-center justify-center md:flex-row">
<div class="mx-auto flex w-11/12 max-w-[600px] flex-col items-center py-10">
<p class=" text-primary-600 text-2xl font-semibold capitalize">{org.name}</p>
<h1 class="text-4xl md:text-5xl lg:text-6xl text-center my-4 font-bold">
<h1 class="my-4 text-center text-4xl font-bold md:text-5xl lg:text-6xl">
{$landingPageSettings.header.title} <br /><span class="text-primary-600"
>{$landingPageSettings.header.titleHighlight}</span
>
@@ -284,9 +283,9 @@
<!-- Our Story section -->
{#if $landingPageSettings.aboutUs.show}
<section id="aboutus" class="m-h-[400px] my-10 mx-auto max-w-6xl w-full">
<div class="mx-4 flex items-center justify-evenly flex-col lg:flex-row">
<div class="max-w-[600px] lg:w-2/5 mr-5 mb-5 lg:mb-0">
<section id="aboutus" class="m-h-[400px] mx-auto my-10 w-full max-w-6xl">
<div class="mx-4 flex flex-col items-center justify-evenly lg:flex-row">
<div class="mb-5 mr-5 max-w-[600px] lg:mb-0 lg:w-2/5">
<h2 class="text-4xl md:text-5xl lg:text-6xl">{$landingPageSettings.aboutUs.title}</h2>
<p class="mb-2">
{$landingPageSettings.aboutUs.content}
@@ -297,7 +296,7 @@
<img
src={$landingPageSettings.aboutUs.imageUrl}
alt="Our Story"
class=" rounded-2xl max-h-[450px]"
class=" max-h-[450px] rounded-2xl"
/>
</div>
</div>
@@ -306,10 +305,10 @@
<!-- Courses Section -->
{#if $landingPageSettings.courses.show}
<section id="courses" transition:fade class="my-10 mx-auto max-w-6xl w-full">
<section id="courses" transition:fade class="mx-auto my-10 w-full max-w-6xl">
<div class="w-full">
<div class="max-w-[500px] mx-auto w-11/12 py-10">
<h1 class="text-4xl md:text-5xl lg:text-6xl text-center my-4 font-bold">
<div class="mx-auto w-11/12 max-w-[500px] py-10">
<h1 class="my-4 text-center text-4xl font-bold md:text-5xl lg:text-6xl">
{$landingPageSettings.courses.title}
<span class="text-primary-600">{$landingPageSettings.courses.titleHighlight}</span>
</h1>
@@ -319,13 +318,13 @@
</div>
</div>
{#if $courseMetaDeta.isLoading}
<div class="cards-container my-4 mx-2">
<div class="cards-container mx-2 my-4">
<CardLoader />
<CardLoader />
<CardLoader />
</div>
{:else if $courses.length > 0}
<div class="cards-container my-4 mx-2">
<div class="cards-container mx-2 my-4">
{#each $courses.slice(0, viewAll ? $courses.length : 3) as courseData}
<Card
id={courseData.id}
@@ -350,17 +349,17 @@
{:else}
<Box>
<CoursesEmptyIcon />
<h3 class="dark:text-white text-2xl my-5">
<h3 class="my-5 text-2xl dark:text-white">
{$t('course.navItem.landing_page.no_course_published')}
</h3>
<p class="dark:text-white w-1/3 text-center">
<p class="w-1/3 text-center dark:text-white">
{$t('course.navItem.landing_page.coming_your_way')}
</p>
</Box>
{/if}
{#if $courses.length > 3}
<div class="w-full mt-3 flex justify-center">
<div class="mt-3 flex w-full justify-center">
<PrimaryButton
variant={VARIANTS.OUTLINED}
onClick={() => (viewAll = !viewAll)}
@@ -376,9 +375,9 @@
<!-- FAQ Section -->
{#if $landingPageSettings.faq.show}
<section id="faq" transition:fade class="my-10 mx-auto max-w-[700px] w-full">
<section id="faq" transition:fade class="mx-auto my-10 w-full max-w-[700px]">
<div class="py-10">
<h1 class="text-4xl md:text-5xl lg:text-6xl text-center my-4 font-bold">
<h1 class="my-4 text-center text-4xl font-bold md:text-5xl lg:text-6xl">
{$landingPageSettings.faq.title}
</h1>
</div>
@@ -398,10 +397,10 @@
<!-- Contact Section-->
{#if $landingPageSettings.contact.show}
<section id="contact" transition:fade class="my-10 w-full bg-primary-50">
<div class="mx-auto max-w-6xl w-full">
<div class="max-w-[500px] mx-auto w-11/12 py-10">
<h1 class="text-4xl md:text-5xl lg:text-6xl text-center my-4 font-bold">
<section id="contact" transition:fade class="bg-primary-50 my-10 w-full">
<div class="mx-auto w-full max-w-6xl">
<div class="mx-auto w-11/12 max-w-[500px] py-10">
<h1 class="my-4 text-center text-4xl font-bold md:text-5xl lg:text-6xl">
{$landingPageSettings.contact.title}
<span class="text-primary-600">{$landingPageSettings.contact.titleHighlight}</span>
</h1>
@@ -411,37 +410,37 @@
<!-- Contact Details -->
<Row>
<Column
class="flex items-center flex-col justify-center break-all text-center cursor-pointer hover:shadow-xl rounded-lg transition-all duration-500 py-2 mx-2"
class="mx-2 flex cursor-pointer flex-col items-center justify-center break-all rounded-lg py-2 text-center transition-all duration-500 hover:shadow-xl"
>
<LocationFilled size={32} />
<p class="text-xs md:text-sm mt-3 max-w-[200px]">
<p class="mt-3 max-w-[200px] text-xs md:text-sm">
{$landingPageSettings.contact.address}
</p>
</Column>
<Column
class="flex items-center flex-col justify-center break-all text-center cursor-pointer hover:shadow-xl rounded-lg transition-all duration-500 py-2 mx-2"
class="mx-2 flex cursor-pointer flex-col items-center justify-center break-all rounded-lg py-2 text-center transition-all duration-500 hover:shadow-xl"
>
<Phone size={32} />
<p class="text-xs md:text-sm mt-3">{$landingPageSettings.contact.phone}</p>
<p class="mt-3 text-xs md:text-sm">{$landingPageSettings.contact.phone}</p>
</Column>
<Column
class="flex items-center flex-col justify-center break-all text-center cursor-pointer hover:shadow-xl rounded-lg transition-all duration-500 py-2 mx-2"
class="mx-2 flex cursor-pointer flex-col items-center justify-center break-all rounded-lg py-2 text-center transition-all duration-500 hover:shadow-xl"
>
<Email size={32} />
<p class="text-xs md:text-sm mt-3">{$landingPageSettings.contact.email}</p>
<p class="mt-3 text-xs md:text-sm">{$landingPageSettings.contact.email}</p>
</Column>
</Row>
<!-- Contact Form -->
<div class="mt-8 bg-white p-7 rounded-lg">
<div class="mt-8 rounded-lg bg-white p-7">
{#if successContactSaved}
<div class="w-full flex items-center justify-center">
<div class="flex w-full items-center justify-center">
{$t('course.navItem.landing_page.thank_you')}
</div>
{:else}
<form on:submit|preventDefault={handleContactSubmit}>
<div class="w-full flex justify-between flex-col md:flex-row">
<div class="w-full md:w-2/4 mr-5">
<div class="flex w-full flex-col justify-between md:flex-row">
<div class="mr-5 w-full md:w-2/4">
<TextField
label={$t('course.navItem.landing_page.name')}
bind:value={contact.name}
@@ -481,7 +480,7 @@
type="submit"
isLoading={isContactSubmiting}
>
<span class="mr-2 text-md">{$t('course.navItem.landing_page.submit')}</span>
<span class="text-md mr-2">{$t('course.navItem.landing_page.submit')}</span>
<Rocket size={24} />
</PrimaryButton>
</form>
@@ -494,12 +493,12 @@
<!-- Waitlist Section -->
{#if $landingPageSettings.mailinglist.show}
<section id="waitlist" transition:fade class="my-10 mx-auto max-w-6xl w-[95%]">
<section id="waitlist" transition:fade class="mx-auto my-10 w-[95%] max-w-6xl">
<div
class="bg-primary-700 rounded-lg flex flex-col lg:flex-row lg:items-center px-4 md:px-10 py-14"
class="bg-primary-700 flex flex-col rounded-lg px-4 py-14 md:px-10 lg:flex-row lg:items-center"
>
<div class="w-full md:w-[65%] md:mr-4">
<h1 class="text-4xl font-bold mb-5 mt-0 text-white">
<div class="w-full md:mr-4 md:w-[65%]">
<h1 class="mb-5 mt-0 text-4xl font-bold text-white">
{$landingPageSettings.mailinglist.title}
</h1>
<p class="text-lg text-white">
@@ -507,7 +506,7 @@
</p>
</div>
<form on:submit|preventDefault={handleSubmit} class="my-4 w-full md:w-fit">
<div class="flex items-center flex-col sm:flex-row">
<div class="flex flex-col items-center sm:flex-row">
{#if success}
<p class="text-white">{$t('course.navItem.landing_page.successful_sub')}</p>
{:else}
@@ -538,9 +537,9 @@
{#if $landingPageSettings.footer.show}
<footer
id="footer"
class="flex justify-center items-center flex-col my-10 w-full px-5 py-10 md:py-3 border-b-0 border-r-0 border-t border-l-0 border-gray-300"
class="my-10 flex w-full flex-col items-center justify-center border-b-0 border-l-0 border-r-0 border-t border-gray-300 px-5 py-10 md:py-3"
>
<ul class="flex w-11/12 items-center flex-col sm:flex-row">
<ul class="flex w-11/12 flex-col items-center sm:flex-row">
<div class="logo">
<a
href="/"
@@ -552,16 +551,16 @@
<img
src={org.avatar_url || '/logo-192.png'}
alt={`${org.name} logo`}
class="rounded max-h-10 w-10 inline-block mx-auto"
class="mx-auto inline-block max-h-10 w-10 rounded"
data-atf="1"
/>
<h3 class="text-black ml-3 text-xl">{org.name}</h3>
<h3 class="ml-3 text-xl text-black">{org.name}</h3>
</a>
</div>
<span class="flex-grow" />
<div class="flex mt-5 sm:mt-0 gap-2">
<div class="mt-5 flex gap-2 sm:mt-0">
{#if $landingPageSettings.footer.facebook}
<li>
<a
@@ -2,13 +2,13 @@
import Modal from '$lib/components/Modal/index.svelte';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import { snackbar } from '$lib/components/Snackbar/store';
import { profile } from '$lib/utils/store/user';
import { t } from '$lib/utils/functions/translations';
import {
triggerSendEmail,
NOTIFICATION_NAME
NOTIFICATION_NAME,
triggerSendEmail
} from '$lib/utils/services/notification/notification';
import { currentOrg } from '$lib/utils/store/org';
import { t } from '$lib/utils/functions/translations';
import { profile } from '$lib/utils/store/user';
const WAIT_SEC = 120;
const WAIT_TIME = WAIT_SEC * 1000;
@@ -51,21 +51,21 @@
}, WAIT_TIME);
};
$: open = Boolean(!$profile.is_email_verified && !!$currentOrg.id);
$: open = Boolean(!$profile.is_email_verified && !!$profile.id && !!$currentOrg.id);
$: open && sendVerificationCode();
</script>
<Modal {open} isCloseable={false} width="w-4/5" maxWidth="w-[500px]" containerClass="p-4">
<div class="flex flex-col items-center space-y-6 text-center">
<img src="/verify-email.svg" alt="email verification" />
<p class="font-bold text-xl">{$t('verify_email_modal.heading')}</p>
<p class="text-sm w-[70%] text-gray-700 dark:text-gray-200">
<p class="text-xl font-bold">{$t('verify_email_modal.heading')}</p>
<p class="w-[70%] text-sm text-gray-700 dark:text-gray-200">
{$t('verify_email_modal.sent_verification')}
{$profile.email}
{$t('verify_email_modal.to_confirm')}
</p>
<div class="flex items-center flex-col">
<div class="flex flex-col items-center">
<PrimaryButton
isDisabled={loading || isSent}
className="font-normal"
@@ -76,7 +76,10 @@ export async function getProfile({
}
// Skip refetching profile, if already in store
if (profileStore.id) return;
if (profileStore.id) {
handleLocaleChange(profileStore.locale);
return;
}
// Check if user has profile
let {
@@ -124,7 +127,7 @@ export async function getProfile({
setAnalyticsUser();
// Fetch language
handleLocaleChange(profileStore.locale);
handleLocaleChange(newProfileData[0].locale);
if (isOrgSite) {
const { data, error } = await supabase
@@ -179,8 +182,7 @@ export async function getProfile({
// Set user in sentry
setAnalyticsUser();
// Fetch language
handleLocaleChange(profileStore.locale);
handleLocaleChange(profileData.locale);
const orgRes = await getOrganizations(profileData.id, isOrgSite, orgSiteName);
@@ -1,6 +1,6 @@
import { LOCALE } from '$lib/utils/types';
import { writable } from 'svelte/store';
import i18n, { type Config } from 'sveltekit-i18n';
import { LOCALE } from '$lib/utils/types';
interface Params {
dateValue: number;
@@ -10,8 +10,7 @@ interface Params {
val: Date;
}
const config: Config<Partial<Params>> = {
initLocale: 'en',
export const config: Config<Partial<Params>> = {
// parser: parser(),
loaders: [
{
@@ -1,7 +1,7 @@
import { t } from '$lib/utils/functions/translations';
import isNumber from 'lodash/isNumber';
import z from 'zod';
import { validateEmail } from './validateEmail';
import { t } from '$lib/utils/functions/translations';
const getOrgNameValidation = () =>
z
@@ -85,12 +85,12 @@ const forgotValidationSchema = z.object({
const authValidationSchema = z.object({
email: z.string().email({
message: `${t.get('validations.auth.email.invalid_email')}`,
invalid_type_error: `${t.get('validations.invalid_type_error')}`
message: 'validations.auth.email.invalid_email',
invalid_type_error: 'validations.invalid_type_error'
}),
password: z.string().min(6, {
message: `${t.get('validations.auth.password.min_char')}`,
invalid_type_error: `${t.get('validations.invalid_type_error')}`
message: 'validations.auth.password.min_char',
invalid_type_error: 'validations.invalid_type_error'
})
});
@@ -1,21 +1,21 @@
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';
import { STATUS } from '$lib/utils/constants/course';
import { isUUID } from '$lib/utils/functions/isUUID';
import { supabase } from '$lib/utils/functions/supabase';
import { isOrgAdmin } from '$lib/utils/store/org';
import type {
Lesson,
Course,
Exercise,
ExerciseTemplate,
Group,
Groupmember,
Exercise,
Lesson,
LessonCompletion,
ExerciseTemplate,
LessonSection
LessonSection,
ProfileCourseProgress
} from '$lib/utils/types';
import { STATUS } from '$lib/utils/constants/course';
import type { PostgrestSingleResponse, PostgrestError } from '@supabase/supabase-js';
import type { ProfileCourseProgress } from '$lib/utils/types';
import { isOrgAdmin } from '$lib/utils/store/org';
import type { PostgrestError, PostgrestSingleResponse } from '@supabase/supabase-js';
import { get } from 'svelte/store';
export async function fetchCourses(profileId, orgId) {
if (!orgId || !profileId) return;
@@ -144,10 +144,7 @@ export async function fetchCourse(courseId?: Course['id'], slug?: Course['slug']
const { data, error } = response;
console.log(`error`, error);
console.log(`data`, data);
if (!data || error) {
console.log(`data`, data);
console.log(`fetchCourse => error`, error);
// return this.redirect(307, '/courses');
return { data, error };
@@ -1,11 +1,11 @@
import { get } from 'svelte/store';
import type { PostgrestError } from '@supabase/supabase-js';
import { goto } from '$app/navigation';
import { supabase } from '$lib/utils/functions/supabase';
import { orgs, currentOrg, orgAudience, orgTeam } from '$lib/utils/store/org';
import { ROLE, ROLE_LABEL } from '$lib/utils/constants/roles';
import type { CurrentOrg, OrgTeamMember } from '$lib/utils/types/org';
import { supabase } from '$lib/utils/functions/supabase';
import { currentOrg, orgAudience, orgs, orgTeam } from '$lib/utils/store/org';
import type { OrganizationPlan } from '$lib/utils/types';
import type { CurrentOrg, OrgTeamMember } from '$lib/utils/types/org';
import type { PostgrestError } from '@supabase/supabase-js';
import { get } from 'svelte/store';
export async function getOrgTeam(orgId: string) {
const { data, error } = await supabase
@@ -164,8 +164,6 @@ export async function getOrgAudience(orgId: string) {
.eq('groupmember.group.organization_id', orgId)
.eq('groupmember.role_id', ROLE.STUDENT);
console.log('data', data);
const audience = (data || []).map((profile) => ({
id: profile.id,
name: profile.fullname,
@@ -1293,7 +1293,29 @@
"email": "Deine E-Mail",
"password": "Ihr Passwort",
"forgot": "Passwort vergessen?",
"login": "Anmeldung"
"login": "Anmeldung",
"login_with": "oder loggen Sie sich mit ein",
"signup_with": "oder melden Sie sich an",
"already_have_account": "Sie haben bereits ein Konto?",
"not_registered_yet": "Noch nicht registriert?",
"signup": "Melden Sie sich an",
"signup_with_google": "Melden Sie sich bei Google an",
"login_with_google": "Melden Sie sich mit Google an",
"create_to_join": "Erstellen Sie ein kostenloses Konto, um Mitglied zu werden",
"login_to_join": "Melden Sie sich an, um beizutreten",
"validation": {
"unable_to_create_profile": "Profil kann nicht erstellt werden",
"user_cannot_be_created": "Benutzer kann nicht erstellt werden"
},
"fields": {
"email": "Ihre E-Mail",
"full_name": "Vollständiger Name",
"password": "Ihr Passwort",
"confirm_password": "Passwort bestätigen",
"password_helper_message": "Das Passwort muss mehr als 6 Zeichen lang sein",
"create_account": "Benutzerkonto erstellen"
},
"create_account": "Benutzerkonto erstellen"
},
"welcome_modal": {
"we_at": "Wir bei",
@@ -49,7 +49,29 @@
"email": "Your email",
"password": "Your password",
"forgot": "Forgot password?",
"login": "Log In"
"login": "Log In",
"login_with": "or login with",
"signup_with": "or sign up with",
"already_have_account": "Already have an account?",
"not_registered_yet": "Not registered yet?",
"signup": "Sign up",
"signup_with_google": "Sign up with Google",
"login_with_google": "Login with Google",
"create_to_join": "Create a free account to join",
"create_account": "Create Account",
"login_to_join": "Log in to join",
"fields": {
"email": "Your Email",
"full_name": "Full Name",
"password": "Your Password",
"confirm_password": "Confirm Password",
"password_helper_message": "Password must be more than 6 characters",
"create_account": "Create Account"
},
"validation": {
"unable_to_create_profile": "Unable to create profile",
"user_cannot_be_created": "User cannot be created"
}
},
"profileMenu": {
"profile": "PROFILE",
@@ -1293,7 +1293,29 @@
"email": "Tu correo electrónico",
"password": "Tu contraseña",
"forgot": "¿Has olvidado tu contraseña?",
"login": "Acceso"
"login": "Acceso",
"login_with": "o inicia sesión con",
"signup_with": "o regístrate con",
"already_have_account": "¿Ya tienes una cuenta?",
"not_registered_yet": "¿Aún no estás registrado?",
"signup": "Inscribirse",
"signup_with_google": "Regístrate con Google",
"login_with_google": "Iniciar sesión con Google",
"create_to_join": "Crea una cuenta gratuita para unirte",
"login_to_join": "Inicia sesión para unirte",
"validation": {
"unable_to_create_profile": "No se puede crear el perfil",
"user_cannot_be_created": "No se puede crear usuario"
},
"fields": {
"email": "Tu correo electrónico",
"full_name": "Nombre completo",
"password": "Tu contraseña",
"confirm_password": "confirmar Contraseña",
"password_helper_message": "La contraseña debe tener más de 6 caracteres.",
"create_account": "Crear una cuenta"
},
"create_account": "Crear una cuenta"
},
"welcome_modal": {
"we_at": "nosotros en",
@@ -1293,7 +1293,29 @@
"email": "Votre email",
"password": "Votre mot de passe",
"forgot": "Mot de passe oublié?",
"login": "Se connecter"
"login": "Se connecter",
"login_with": "ou connectez-vous avec",
"signup_with": "ou inscrivez-vous avec",
"already_have_account": "Vous avez déjà un compte ?",
"not_registered_yet": "Pas encore inscrit ?",
"signup": "S'inscrire",
"signup_with_google": "Inscrivez-vous avec Google",
"login_with_google": "Connectez-vous avec Google",
"create_to_join": "Créez un compte gratuit pour vous inscrire",
"login_to_join": "Connectez-vous pour rejoindre",
"validation": {
"unable_to_create_profile": "Impossible de créer un profil",
"user_cannot_be_created": "L'utilisateur ne peut pas être créé"
},
"fields": {
"email": "Votre e-mail",
"full_name": "Nom et prénom",
"password": "Votre mot de passe",
"confirm_password": "Confirmez le mot de passe",
"password_helper_message": "Le mot de passe doit comporter plus de 6 caractères",
"create_account": "Créer un compte"
},
"create_account": "Créer un compte"
},
"welcome_modal": {
"we_at": "Nous à",
@@ -1293,7 +1293,29 @@
"email": "आपका ईमेल",
"password": "आपका पासवर्ड",
"forgot": "पासवर्ड भूल गए?",
"login": "लॉग इन करें"
"login": "लॉग इन करें",
"login_with": "या के साथ लॉगिन करें",
"signup_with": "या इसके साथ साइन अप करें",
"already_have_account": "क्या आपके पास पहले से एक खाता मौजूद है?",
"not_registered_yet": "अभी तक पंजीकृत नहीं हैं?",
"signup": "साइन अप करें",
"signup_with_google": "Google के साथ साइन अप करें",
"login_with_google": "गूगल से लॉगिन करें",
"create_to_join": "शामिल होने के लिए एक निःशुल्क खाता बनाएँ",
"login_to_join": "शामिल होने के लिए लॉग इन करें",
"validation": {
"unable_to_create_profile": "प्रोफ़ाइल बनाने में असमर्थ",
"user_cannot_be_created": "उपयोगकर्ता नहीं बनाया जा सकता"
},
"fields": {
"email": "आपका ईमेल",
"full_name": "पूरा नाम",
"password": "आपका पासवर्ड",
"confirm_password": "पासवर्ड की पुष्टि कीजिये",
"password_helper_message": "पासवर्ड 6 अक्षर से अधिक होना चाहिए",
"create_account": "खाता बनाएं"
},
"create_account": "खाता बनाएं"
},
"welcome_modal": {
"we_at": "हम पर",
@@ -1293,7 +1293,29 @@
"email": "Seu email",
"password": "Sua senha",
"forgot": "Esqueceu sua senha?",
"login": "Conecte-se"
"login": "Conecte-se",
"login_with": "ou faça login com",
"signup_with": "ou cadastre-se com",
"already_have_account": "Já tem uma conta?",
"not_registered_yet": "Ainda não está cadastrado?",
"signup": "Inscrever-se",
"signup_with_google": "Inscreva-se no Google",
"login_with_google": "Faça login com o Google",
"create_to_join": "Crie uma conta gratuita para participar",
"login_to_join": "Faça login para participar",
"validation": {
"unable_to_create_profile": "Não foi possível criar o perfil",
"user_cannot_be_created": "O usuário não pode ser criado"
},
"fields": {
"email": "Seu e-mail",
"full_name": "Nome completo",
"password": "Sua senha",
"confirm_password": "Confirme sua senha",
"password_helper_message": "A senha deve ter mais de 6 caracteres",
"create_account": "Criar uma conta"
},
"create_account": "Criar uma conta"
},
"welcome_modal": {
"we_at": "Nós em",
@@ -1293,7 +1293,29 @@
"email": "Ваш адрес электронной почты",
"password": "Ваш пароль",
"forgot": "Забыли пароль?",
"login": "Авторизоваться"
"login": "Авторизоваться",
"login_with": "или войдите с помощью",
"signup_with": "или зарегистрируйтесь через",
"already_have_account": "У вас уже есть аккаунт?",
"not_registered_yet": "Еще не зарегистрированы?",
"signup": "Зарегистрироваться",
"signup_with_google": "Зарегистрируйтесь через Google",
"login_with_google": "Войти через Google",
"create_to_join": "Создайте бесплатную учетную запись, чтобы присоединиться",
"login_to_join": "Войдите, чтобы присоединиться",
"validation": {
"unable_to_create_profile": "Невозможно создать профиль",
"user_cannot_be_created": "Пользователь не может быть создан"
},
"fields": {
"email": "Ваш адрес электронной почты",
"full_name": "Полное имя",
"password": "Ваш пароль",
"confirm_password": "Подтвердите пароль",
"password_helper_message": "Пароль должен быть более 6 символов",
"create_account": "Зарегистрироваться"
},
"create_account": "Зарегистрироваться"
},
"welcome_modal": {
"we_at": "Мы в",
@@ -1293,7 +1293,29 @@
"email": "Email của bạn",
"password": "Mật khẩu của bạn",
"forgot": "Quên mật khẩu?",
"login": "Đăng nhập"
"login": "Đăng nhập",
"login_with": "hoặc đăng nhập với",
"signup_with": "hoặc đăng ký với",
"already_have_account": "Đã có tài khoản?",
"not_registered_yet": "Chưa đăng ký?",
"signup": "Đăng ký",
"signup_with_google": "Đăng ký với Google",
"login_with_google": "Đăng nhập bằng Google",
"create_to_join": "Tạo một tài khoản miễn phí để tham gia",
"login_to_join": "Đăng nhập để tham gia",
"validation": {
"unable_to_create_profile": "Không thể tạo hồ sơ",
"user_cannot_be_created": "Không thể tạo người dùng"
},
"fields": {
"email": "Email của bạn",
"full_name": "Tên đầy đủ",
"password": "Mật khẩu của bạn",
"confirm_password": "Xác nhận mật khẩu",
"password_helper_message": "Mật khẩu phải nhiều hơn 6 ký tự",
"create_account": "Tạo tài khoản"
},
"create_account": "Tạo tài khoản"
},
"welcome_modal": {
"we_at": "Chúng tôi ở",
+12 -29
View File
@@ -1,13 +1,12 @@
import type { MetaTagsProps } from 'svelte-meta-tags';
import { dev, browser } from '$app/environment';
import { redirect } from '@sveltejs/kit';
import { blockedSubdomain } from '$lib/utils/constants/app';
import { getCurrentOrg } from '$lib/utils/services/org';
import { getSupabase, supabase } from '$lib/utils/functions/supabase';
import { loadTranslations } from '$lib/utils/functions/translations';
import type { CurrentOrg } from '$lib/utils/types/org';
import { IS_SELFHOSTED } from '$env/static/private';
import { dev } from '$app/environment';
import { env } from '$env/dynamic/private';
import { IS_SELFHOSTED } from '$env/static/private';
import { blockedSubdomain } from '$lib/utils/constants/app';
import { getSupabase, supabase } from '$lib/utils/functions/supabase';
import { getCurrentOrg } from '$lib/utils/services/org';
import type { CurrentOrg } from '$lib/utils/types/org';
import { redirect } from '@sveltejs/kit';
import type { MetaTagsProps } from 'svelte-meta-tags';
if (!supabase) {
getSupabase();
@@ -19,19 +18,19 @@ interface LoadOutput {
skipAuth: boolean;
org: CurrentOrg | null;
baseMetaTags: MetaTagsProps;
serverLang: string;
}
export const load = async ({ url, cookies }): Promise<LoadOutput> => {
export const load = async ({ url, cookies, request }): Promise<LoadOutput> => {
const response: LoadOutput = {
orgSiteName: '',
isOrgSite: false,
skipAuth: false,
org: null,
baseMetaTags: getBaseMetaTags(url)
baseMetaTags: getBaseMetaTags(url),
serverLang: request.headers?.get('accept-language') || ''
};
console.log('IS_SELFHOSTED', IS_SELFHOSTED);
// Selfhosted usecase would be here
if (IS_SELFHOSTED === 'true') {
const subdomain = getSubdomain(url);
@@ -105,11 +104,6 @@ export const load = async ({ url, cookies }): Promise<LoadOutput> => {
throw redirect(307, 'https://app.classroomio.com');
}
// Load translations
const { pathname } = url;
const initLocale = getInitialLocale();
await loadTranslations(initLocale, pathname);
return response;
};
@@ -151,18 +145,7 @@ function getBaseMetaTags(url: URL) {
});
}
// Define getInitialLocale function
function getInitialLocale(): string {
if (browser) {
try {
return window.navigator.language.split('-')[0];
} catch (e) {
return 'en';
}
}
return 'en';
}
function getSubdomain(url: URL) {
const host = url.host.replace('www.', '');
+7 -6
View File
@@ -1,6 +1,8 @@
<script lang="ts">
import { browser } from '$app/environment';
import { page } from '$app/stores';
import debounce from 'lodash/debounce';
import Apps from '$lib/components/Apps/index.svelte';
import { course } from '$lib/components/Course/store';
import OrgNavigation from '$lib/components/Navigation/app.svelte';
@@ -22,7 +24,6 @@
import { globalStore } from '$lib/utils/store/app';
import { currentOrg } from '$lib/utils/store/org';
import { isMobile } from '$lib/utils/store/useMobile';
import { user } from '$lib/utils/store/user';
import { Theme } from 'carbon-components-svelte';
import type { CarbonTheme } from 'carbon-components-svelte/types/Theme/Theme.svelte';
import merge from 'lodash/merge';
@@ -42,6 +43,8 @@
isMobile.update(() => window.innerWidth <= 760);
}
const getProfileDebounced = debounce(getProfile, 1000);
onMount(() => {
console.log(
'Welcome to ClassroomIO, we are grateful you chose us.',
@@ -77,18 +80,15 @@
}
// Skip Authentication
if (data.skipAuth || $user.fetchingUser) return;
if (data.skipAuth) return;
// Authentication Steps
if (event === 'SIGNED_IN' || event === 'INITIAL_SESSION') {
$user.fetchingUser = true;
getProfile({
getProfileDebounced({
path,
queryParam,
isOrgSite: data.isOrgSite,
orgSiteName: data.orgSiteName
}).then(() => {
$user.fetchingUser = false;
});
}
// else if (!['TOKEN_REFRESHED'].includes(event)) {
@@ -125,6 +125,7 @@
<Theme bind:theme={carbonTheme} />
<UpgradeModal />
<Snackbar />
{#if data.org?.is_restricted || $currentOrg.is_restricted}
+28
View File
@@ -0,0 +1,28 @@
import { config, loadTranslations } from '$lib/utils/functions/translations';
import { profile } from '$lib/utils/store/user';
import { get } from 'svelte/store';
const SUPPORTED_LANGUAGES = config?.loaders?.map((loader) => loader.locale) || [];
export const load = async ({ url, data }) => {
const { pathname } = url;
const profileStore = get(profile);
const serverLang = data.serverLang.split('-')[0];
const userLocale = profileStore.id ? profileStore.locale : getInitialLocale(serverLang);
const initLocale = getInitialLocale(userLocale);
await loadTranslations(initLocale, pathname); // keep this just before the `return`
return data;
};
function getInitialLocale(lang: string): string {
const locale = lang.split('-')[0];
if (SUPPORTED_LANGUAGES.includes(locale)) return locale;
return 'en';
}
@@ -1,21 +1,22 @@
<script lang="ts">
import { goto } from '$app/navigation';
import AuthUI from '$lib/components/AuthUI/index.svelte';
import type { Profile } from '$lib/components/Course/components/People/types';
import TextField from '$lib/components/Form/TextField.svelte';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import { SIGNUP_FIELDS } from '$lib/utils/constants/authentication';
import { logout } from '$lib/utils/functions/logout';
import { getSupabase } from '$lib/utils/functions/supabase';
import { setTheme } from '$lib/utils/functions/theme';
import { t } from '$lib/utils/functions/translations';
import {
authValidation,
getConfirmPasswordError,
getDisableSubmit
} from '$lib/utils/functions/validator';
import { SIGNUP_FIELDS } from '$lib/utils/constants/authentication';
import AuthUI from '$lib/components/AuthUI/index.svelte';
import { currentOrg, currentOrgPath } from '$lib/utils/store/org';
import { setTheme } from '$lib/utils/functions/theme';
import type { CurrentOrg } from '$lib/utils/types/org';
import { onMount } from 'svelte';
import type { Profile } from '$lib/components/Course/components/People/types';
import { logout } from '$lib/utils/functions/logout';
export let data;
@@ -61,7 +62,7 @@
if (error) throw error;
if (!signupData.user) {
throw 'User cannot be created ';
throw $t('login.validation.user_cannot_be_created');
}
// Insert profile
@@ -86,7 +87,7 @@
}
if (!profile?.id) {
throw 'Unable to create profile';
throw $t('login.validation.unable_to_create_profile');
}
const res = await supabase.auth.signInWithPassword({
@@ -142,9 +143,6 @@
$: disableSubmit = getDisableSubmit(fields);
$: console.log('data', data.invite);
$: console.log('org', $currentOrg);
$: console.log('path', $currentOrgPath);
</script>
<svelte:head>
@@ -153,15 +151,15 @@
<AuthUI {supabase} isLogin={false} {handleSubmit} isLoading={loading} showLogo={true} bind:formRef>
<div class="mt-4 w-full">
<p class="dark:text-white text-lg font-semibold mb-6">
<p class="mb-6 text-lg font-semibold dark:text-white">
{#if data.invite.profile}
Log in to join
{$t('login.login_to_join')}
{:else}
Create a free account to join
{$t('login.create_to_join')}
{/if}
</p>
<TextField
label="Your Email"
label={$t('login.fields.email')}
value={data.invite.email}
type="email"
placeholder="you@domain.com"
@@ -171,7 +169,7 @@
/>
{#if !data.invite.profile}
<TextField
label="Full Name"
label={$t('login.fields.full_name')}
bind:value={fields.name}
type="text"
autoFocus={true}
@@ -184,7 +182,7 @@
/>
{/if}
<TextField
label="Your Password"
label={$t('login.fields.password')}
bind:value={fields.password}
type="password"
placeholder="************"
@@ -192,12 +190,12 @@
inputClassName="w-full"
isDisabled={loading}
errorMessage={errors.password}
helperMessage="Password must be more than 6 characters"
helperMessage={$t('login.fields.password_helper_message')}
isRequired
/>
{#if !data.invite.profile}
<TextField
label="Confirm Password"
label={$t('login.fields.confirm_password')}
bind:value={fields.confirmPassword}
type="password"
placeholder="************"
@@ -213,7 +211,7 @@
{/if}
</div>
<div class="my-4 w-full flex justify-end items-center">
<div class="my-4 flex w-full items-center justify-end">
<PrimaryButton
label="Accept Invite"
type="submit"
+10 -14
View File
@@ -1,15 +1,14 @@
<script lang="ts">
import { page } from '$app/stores';
import AuthUI from '$lib/components/AuthUI/index.svelte';
import TextField from '$lib/components/Form/TextField.svelte';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import { getSupabase } from '$lib/utils/functions/supabase';
import { authValidation } from '$lib/utils/functions/validator';
import { LOGIN_FIELDS } from '$lib/utils/constants/authentication';
import AuthUI from '$lib/components/AuthUI/index.svelte';
import { currentOrg } from '$lib/utils/store/org';
import { capturePosthogEvent } from '$lib/utils/services/posthog';
import { getSupabase } from '$lib/utils/functions/supabase';
import { t } from '$lib/utils/functions/translations';
import { authValidation } from '$lib/utils/functions/validator';
import { capturePosthogEvent } from '$lib/utils/services/posthog';
import { globalStore } from '$lib/utils/store/app';
import { currentOrg } from '$lib/utils/store/org';
let formRef: HTMLFormElement;
let supabase = getSupabase();
@@ -18,9 +17,6 @@
let loading = false;
let errors = Object.assign({}, LOGIN_FIELDS);
let query = new URLSearchParams($page.url.search);
let redirect = query.get('redirect');
async function handleSubmit() {
const validationRes = authValidation(fields);
console.log('validationRes', validationRes);
@@ -48,7 +44,7 @@
email: fields.email
});
}
} catch (error) {
} catch (error: any) {
submitError = error.error_description || error.message;
loading = false;
}
@@ -61,7 +57,7 @@
<AuthUI {supabase} isLogin={true} {handleSubmit} isLoading={loading} bind:formRef>
<div class="mt-4 w-full">
<p class="dark:text-white text-lg font-semibold mb-6">{$t('login.welcome')}</p>
<p class="mb-6 text-lg font-semibold dark:text-white">{$t('login.welcome')}</p>
<TextField
label={$t('login.email')}
bind:value={fields.email}
@@ -71,7 +67,7 @@
className="mb-6"
inputClassName="w-full"
isDisabled={loading}
errorMessage={errors.email}
errorMessage={$t(errors.email)}
/>
<TextField
label={$t('login.password')}
@@ -81,7 +77,7 @@
className="mb-6"
inputClassName="w-full"
isDisabled={loading}
errorMessage={errors.password}
errorMessage={$t(errors.password)}
/>
{#if submitError}
<p class="text-sm text-red-500">{submitError}</p>
@@ -91,7 +87,7 @@
</div>
</div>
<div class="my-4 w-full flex justify-end items-center">
<div class="my-4 flex w-full items-center justify-end">
<!-- <a href="/login" class="text-primary-700 text-sm">Create an account</a> -->
<PrimaryButton
label={$t('login.login')}
@@ -9,6 +9,23 @@ async function getGenericData(orgSlug: string): Promise<{
orgHasAvatarUrl: boolean;
courseData: any;
}> {
const { data: publishedCourse } = await supabase
.from('course')
.select(
`
is_published,
group:group_id!inner(
organization!inner(
siteName
)
)
`
)
.eq('is_published', true)
.eq('group.organization.siteName', orgSlug)
.limit(1)
.returns<{ is_published: boolean }>();
const { data } = await supabase
.from('course')
.select(
@@ -23,14 +40,13 @@ async function getGenericData(orgSlug: string): Promise<{
)
`
)
.eq('group.organization.siteName', orgSlug);
.eq('group.organization.siteName', orgSlug)
.limit(1);
const result = data || [];
console.log('courseData', result);
return {
isCoursePublished: result.some((c) => c.is_published === true),
isCoursePublished: publishedCourse?.is_published === true,
isCourseCreated: result.length > 0,
orgHasAvatarUrl: !!get(result[0], 'group.organization.avatar_url', ''),
courseData: result
@@ -54,7 +70,8 @@ async function getIsLessonCreated(
)
`
)
.eq('course.group.organization.siteName', orgSlug);
.eq('course.group.organization.siteName', orgSlug)
.limit(1);
console.log('lessonsData', data);
return {
@@ -80,7 +97,8 @@ async function getIsExerciseCreated(orgSlug: string): Promise<boolean> {
)
`
)
.eq('lesson.course.group.organization.siteName', orgSlug);
.eq('lesson.course.group.organization.siteName', orgSlug)
.limit(1);
return Array.isArray(data) ? data?.length > 0 : false;
}
+16 -15
View File
@@ -1,21 +1,22 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import AuthUI from '$lib/components/AuthUI/index.svelte';
import TextField from '$lib/components/Form/TextField.svelte';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import SenjaEmbed from '$lib/components/Senja/Embed.svelte';
import { SIGNUP_FIELDS } from '$lib/utils/constants/authentication';
import { getSupabase } from '$lib/utils/functions/supabase';
import { t } from '$lib/utils/functions/translations';
import {
authValidation,
getConfirmPasswordError,
getDisableSubmit
} from '$lib/utils/functions/validator';
import { SIGNUP_FIELDS } from '$lib/utils/constants/authentication';
import AuthUI from '$lib/components/AuthUI/index.svelte';
import { profile } from '$lib/utils/store/user';
import { currentOrg } from '$lib/utils/store/org';
import { capturePosthogEvent } from '$lib/utils/services/posthog';
import { globalStore } from '$lib/utils/store/app';
import SenjaEmbed from '$lib/components/Senja/Embed.svelte';
import { currentOrg } from '$lib/utils/store/org';
import { profile } from '$lib/utils/store/user';
let supabase = getSupabase();
let fields = Object.assign({}, SIGNUP_FIELDS);
@@ -112,7 +113,7 @@
formRef?.reset();
success = true;
fields = Object.assign({}, SIGNUP_FIELDS);
} catch (error) {
} catch (error: any) {
submitError = error?.error_description || error?.message;
loading = false;
}
@@ -130,7 +131,7 @@
<AuthUI {supabase} isLogin={false} {handleSubmit} isLoading={loading} bind:formRef>
<div class="mt-4 w-full">
<p class="dark:text-white text-lg font-semibold mb-6">Create a free account</p>
<p class="mb-6 text-lg font-semibold dark:text-white">Create a free account</p>
<!-- <TextField
label="Full Name"
bind:value={fields.name}
@@ -144,30 +145,30 @@
isRequired
/> -->
<TextField
label="Your Email"
label={$t('login.fields.email')}
bind:value={fields.email}
type="email"
placeholder="you@domain.com"
className="mb-6"
inputClassName="w-full"
isDisabled={loading}
errorMessage={errors.email}
errorMessage={$t(errors.email ?? '')}
isRequired
/>
<TextField
label="Your Password"
label={$t('login.fields.password')}
bind:value={fields.password}
type="password"
placeholder="************"
className="mb-6"
inputClassName="w-full"
isDisabled={loading}
errorMessage={errors.password}
helperMessage="Password must be more than 6 characters"
errorMessage={$t(errors.password ?? '')}
helperMessage={$t('login.fields.password_helper_message')}
isRequired
/>
<TextField
label="Confirm Password"
label={$t('login.fields.confirm_password')}
bind:value={fields.confirmPassword}
type="password"
placeholder="************"
@@ -182,9 +183,9 @@
{/if}
</div>
<div class="my-4 w-full flex justify-end items-center">
<div class="my-4 flex w-full items-center justify-end">
<PrimaryButton
label="Create Account"
label={$t('login.create_account')}
type="submit"
className="sm:w-full w-full"
isDisabled={disableSubmit || loading}