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:
Vendored
+1
-1
@@ -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 ở",
|
||||
|
||||
@@ -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.', '');
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user