feat: multi language support (#254)

* feat: multitranslational support

* - completion of settings page
- completion of sidebar

* sidebar rerendering bug fix

* completion of the lesson navigation & materials page

* completion of the course navigation

* completion of requested languages

* - Implementation of Language Picker
- Rename Bug Fixes

* checkpoint save

* - addition of the language picker to the onboarding.
- addition of the language switcher for the lesson notes.

* merge completion

* initialization of the new branch

* add transition.svelte file

* lesson note bug fix

* feat: add translation migration

* completion of translation

* fix height issue of dropdown

* refactoring

* fix: improve code with LOCALE from db

* finally get it fully functional

* connect with AI

* backward compatibility for lesson note

* preserve old content, when generating AI content

* undo translations in upgrade modal

* fix duplicate key issue from codacy trigger

---------

Co-authored-by: Rotimi Best <best@classroomio.com>
This commit is contained in:
Adeyemi Adetunji
2024-04-19 05:45:48 +01:00
committed by GitHub
co-authored by Rotimi Best
parent ff7d2cee35
commit d75e2f258e
153 changed files with 11264 additions and 1130 deletions
@@ -11,6 +11,7 @@
import { snackbar } from '$lib/components/Snackbar/store';
import { createOrgValidation } from '$lib/utils/functions/validator';
import { goto } from '$app/navigation';
import { t } from '$lib/utils/functions/translations';
type Error = {
orgName: string;
@@ -84,8 +85,7 @@
if (error) {
console.log('Error: create organisation member', error);
errors.siteName =
'Something went wrong while creating this organization. Please reload and try again';
errors.siteName = $t('add_org.error_organization');
// Delete organization so it can be recreated.
await supabase.from('organization').delete().match({ siteName });
@@ -109,11 +109,11 @@
onClose={() => ($newOrgModal.open = false)}
bind:open={$newOrgModal.open}
width="w-96"
modalHeading="Create Organization"
modalHeading={$t('add_org.create_org')}
>
<form on:submit|preventDefault={createNewOrg} class="px-2">
<TextField
label="Name of Organization"
label={$t('add_org.name')}
bind:value={orgName}
autoFocus={true}
placeholder="e.g Pepsi Co"
@@ -124,7 +124,7 @@
/>
<!-- Org Site Name -->
<TextField
label="Organisation Site name"
label={$t('add_org.org_sitename')}
helperMessage={`https://${siteName || ''}.classroomio.com`}
bind:value={siteName}
name="sitename"
@@ -137,7 +137,12 @@
/>
<div class="mt-5 flex items-center flex-row-reverse">
<PrimaryButton className="px-6 py-3" label="Create" type="submit" isLoading={loading} />
<PrimaryButton
className="px-6 py-3"
label={$t('add_org.create')}
type="submit"
isLoading={loading}
/>
</div>
</form>
</Modal>
@@ -5,16 +5,17 @@
ToolbarContent,
ToolbarSearch,
Pagination,
DataTableSkeleton,
DataTableSkeleton
} from 'carbon-components-svelte';
import { orgAudience } from '$lib/utils/store/org';
import { t } from '$lib/utils/functions/translations';
export let isLoading = false;
const headers = [
{ key: 'name', value: 'Name' },
{ key: 'email', value: 'Email' },
{ key: 'date_joined', value: 'Date Joined' },
{ key: 'name', value: $t('audience.name') },
{ key: 'email', value: $t('audience.email') },
{ key: 'date_joined', value: $t('audience.date_joined') }
];
let pageSize = 5;
let page = 1;
@@ -28,20 +29,10 @@
<DataTable size="tall" {headers} rows={$orgAudience} {pageSize} {page}>
<Toolbar>
<ToolbarContent>
<ToolbarSearch
persistent
value={searchValue}
shouldFilterRows
bind:filteredRowIds
/>
<ToolbarSearch persistent value={searchValue} shouldFilterRows bind:filteredRowIds />
</ToolbarContent>
</Toolbar>
</DataTable>
{/if}
<Pagination
bind:pageSize
bind:page
totalItems={filteredRowIds.length}
pageSizeInputDisabled
/>
<Pagination bind:pageSize bind:page totalItems={filteredRowIds.length} pageSizeInputDisabled />
@@ -4,6 +4,7 @@
import AudienceEmptyIcon from '$lib/components/Icons/AudienceEmptyIcon.svelte';
import { getOrgAudience } from '$lib/utils/services/org';
import { orgAudience, currentOrg } from '$lib/utils/store/org';
import { t } from '$lib/utils/functions/translations';
let isLoading = false;
@@ -24,9 +25,9 @@
{:else}
<Box>
<AudienceEmptyIcon />
<h3 class="dark:text-white text-2xl text-center my-5">No audience to manage!</h3>
<h3 class="dark:text-white text-2xl text-center my-5">{$t('audience.no_audience')}!</h3>
<p class="dark:text-white w-1/3 text-center">
Manage all your students here and stay connected with them.
{$t('audience.manage')}.
</p>
</Box>
{/if}
@@ -2,6 +2,7 @@
import Modal from '$lib/components/Modal/index.svelte';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
import { t } from '$lib/utils/functions/translations';
export let onDelete = () => {};
export let onCancel = () => {};
@@ -14,24 +15,27 @@
onClose={() => (open = false)}
bind:open
width="w-96"
modalHeading="Delete {isQuestion ? 'question' : 'comment'}"
modalHeading="{$t('community.delete.title')} {isQuestion
? $t('community.delete.question')
: $t('community.delete.comment')}"
>
<div>
<h1 class="dark:text-white text-lg">
Are you sure you want to delete this {isQuestion ? 'question' : 'comment'}?
{$t('community.delete.sure')}
{isQuestion ? $t('community.delete.question') : $t('community.delete.comment')}?
</h1>
<div class="mt-5 flex items-center justify-between">
<PrimaryButton
className="px-6 py-3"
variant={VARIANTS.OUTLINED}
label="No, keep it"
label={$t('community.delete.no')}
onClick={onCancel}
/>
<PrimaryButton
className="px-6 py-3"
variant={VARIANTS.CONTAINED}
label="Yes, delete"
label={$t('community.delete.yes')}
onClick={onDelete}
isLoading={isDeleting}
/>
@@ -12,6 +12,7 @@
import { fetchCourses } from '$lib/components/Courses/api';
import { profile } from '$lib/utils/store/user';
import { Search, Dropdown } from 'carbon-components-svelte';
import { t } from '$lib/utils/functions/translations';
export let isLMS = false;
@@ -85,15 +86,15 @@
<div class="flex justify-between">
<Search
placeholder="Find Question"
placeholder={$t('community.find_question')}
bind:value={searchValue}
searchClass="mr-2"
class=" bg-gray-100 dark:bg-neutral-800"
/>
<Dropdown
class="w-[25%]"
class="w-[25%] h-full"
size="xl"
label="Select Course"
label={$t('community.ask.select_course')}
items={allCourses.map((course) => ({ id: course.id, text: course.title }))}
bind:selectedId
/>
@@ -137,8 +138,8 @@
{:else}
<Box className="w-screen">
<CoursesEmptyIcon />
<h3 class="dark:text-white text-2xl my-5">No Questions asked</h3>
<p class="dark:text-white w-1/3 text-center">Ask a question to the community</p>
<h3 class="dark:text-white text-2xl my-5">{$t('community.no_question')}</h3>
<p class="dark:text-white w-1/3 text-center">{$t('community.ask_a_question')}</p>
</Box>
{/each}
{/if}
@@ -1,5 +1,6 @@
<script>
import { Search } from 'carbon-components-svelte';
import { t } from '$lib/utils/functions/translations';
let searchValue = '';
</script>
@@ -9,13 +10,16 @@
<!-- Course Description -->
<div class="course-descr w-11/12 py-10">
<h1 class="dark:text-white text-5xl text-white text-center my-4 font-bold">
Explore courses
{$t('course.navItem.landing_page.explore')}
</h1>
<p class="dark:text-white text-md text-white mb-6 text-center">
Find courses you will love from best teachers all over the world🌎.
{$t('course.navItem.landing_page.find')}🌎.
</p>
<Search placeholder="Find a course..." bind:value={searchValue} />
<Search
placeholder={$t('course.navItem.landing_page.find_course')}
bind:value={searchValue}
/>
</div>
</div>
</section>
@@ -27,6 +27,7 @@
import TextArea from '$lib/components/Form/TextArea.svelte';
import { goto } from '$app/navigation';
import { landingPageSettings } from '$lib/components/Org/Settings/store';
import { t } from '$lib/utils/functions/translations';
import PoweredBy from '$lib/components/Upgrade/PoweredBy.svelte';
export let orgSiteName = '';
@@ -148,7 +149,7 @@
<svelte:head>
<title>
{!org.name ? '' : `${org.name}'s `}Landing Page
{!org.name ? '' : `${org.name}'s `}{$t('course.navItem.landing_page.landing_page')}
</title>
</svelte:head>
@@ -198,7 +199,7 @@
{#if isYouTubeLink($landingPageSettings.header?.banner?.video) && $landingPageSettings.header.banner.type === 'video'}
<div bind:this={player} id="player" style="width:100%; border-radius:12px">
<iframe
title="Header video"
title={$t('course.navItem.landing_page.header_video')}
src={$landingPageSettings.header?.banner?.video}
allowfullscreen
allowtransparency
@@ -311,9 +312,11 @@
{:else}
<Box>
<CoursesEmptyIcon />
<h3 class="dark:text-white text-2xl my-5">No Course Published</h3>
<h3 class="dark:text-white text-2xl my-5">
{$t('course.navItem.landing_page.no_course_published')}
</h3>
<p class="dark:text-white w-1/3 text-center">
We've got great courses coming your way, stay tuned!!!
{$t('course.navItem.landing_page.coming_your_way')}
</p>
</Box>
{/if}
@@ -323,7 +326,9 @@
<PrimaryButton
variant={VARIANTS.OUTLINED}
onClick={() => (viewAll = !viewAll)}
label={viewAll ? 'View Less' : 'View All'}
label={viewAll
? $t('course.navItem.landing_page.view_less')
: $t('course.navItem.landing_page.view_all')}
className="px-10 py-5 w-fit"
/>
</div>
@@ -393,14 +398,14 @@
<div class="mt-8 bg-white p-7 rounded-lg">
{#if successContactSaved}
<div class="w-full flex items-center justify-center">
Thank you for dropping a message, we will get back to you shortly
{$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">
<TextField
label="Your Name"
label={$t('course.navItem.landing_page.name')}
bind:value={contact.name}
errorMessage={contactError.name}
className="mb-5"
@@ -408,14 +413,14 @@
placeholder="Elon Musk"
/>
<TextField
label="Your Email"
label={$t('course.navItem.landing_page.email')}
bind:value={contact.email}
errorMessage={contactError.email}
className="text-xs font-normal mb-5"
placeholder="musk@x.com"
/>
<TextField
label="Your Phone"
label={$t('course.navItem.landing_page.phone')}
bind:value={contact.phone}
errorMessage={contactError.phone}
className="text-xs font-normal mb-5"
@@ -424,12 +429,12 @@
</div>
<div class="w-full md:w-2/4">
<TextArea
label="Your Message"
label={$t('course.navItem.landing_page.message')}
bind:value={contact.message}
errorMessage={contactError.message}
rows="9"
maxRows={15}
placeholder="Your message here"
placeholder={$t('course.navItem.landing_page.your_message')}
/>
</div>
</div>
@@ -439,7 +444,7 @@
type="submit"
isLoading={isContactSubmiting}
>
<span class="mr-2 text-md">Submit</span>
<span class="mr-2 text-md">{$t('course.navItem.landing_page.submit')}</span>
<Rocket size={24} />
</PrimaryButton>
</form>
@@ -467,12 +472,12 @@
<form on:submit|preventDefault={handleSubmit} class="my-4 w-full md:w-fit">
<div class="flex items-center flex-col sm:flex-row">
{#if success}
<p class="text-white">You have been added successfully. Thanks for subscribing.</p>
<p class="text-white">{$t('course.navItem.landing_page.successful_sub')}</p>
{:else}
<TextField
bind:value={email}
type="email"
placeholder="Enter your email.."
placeholder={$t('course.navItem.landing_page.enter')}
className="sm:mr-3 my-0 w-full md:w-fit"
isRequired={true}
isDisabled={isAdding}
@@ -525,7 +530,7 @@
<a
href={$landingPageSettings.footer.facebook}
target="_blank"
title="Facebook"
title={$t('settings.landing_page.footer.facebook')}
id="logo-fb"
data-hveid="8"
>
@@ -538,7 +543,7 @@
<a
href={$landingPageSettings.footer.twitter}
target="_blank"
title="Twitter"
title={$t('settings.landing_page.footer.twitter')}
id="logo-tw"
data-hveid="8"
>
@@ -552,7 +557,7 @@
<a
href={$landingPageSettings.footer.linkedin}
target="_blank"
title="Linkedin"
title={$t('settings.landing_page.footer.linkedin')}
id="logo-ln"
data-hveid="8"
>
@@ -4,6 +4,7 @@
import Modal from '$lib/components/Modal/index.svelte';
import { deleteModal, quizesStore } from '$lib/utils/store/org';
import { supabase } from '$lib/utils/functions/supabase';
import { t } from '$lib/utils/functions/translations';
export let onDelete = () => {};
@@ -34,24 +35,26 @@
onClose={closeModal}
bind:open={$deleteModal.open}
width="w-2/5"
modalHeading={$deleteModal.isQuestion ? 'Delete Question' : 'Delete Quiz'}
modalHeading={$deleteModal.isQuestion
? $t('components.quiz.question_delete')
: $t('components.quiz.quiz_delete')}
size="sm"
>
<div class="w-full flex flex-col items-center my-10">
<h1 class="dark:text-white text-lg text-center">
{#if $deleteModal.isQuestion}
Are you sure you want to delete this question?
{$t('components.quiz.delete_question')}
{:else}
Are you sure you want to delete this quiz?
{$t('components.quiz.delete_quiz')}
{/if}
You will lose your content and this action can’t be undone.
{$t('components.quiz.lose_content')}
</h1>
<div class="mt-5">
<PrimaryButton
className="px-6 py-3"
variant={VARIANTS.CONTAINED}
label="Delete"
label={$t('components.quiz.delete')}
onClick={() => {
if ($deleteModal.isQuestion) {
onDelete();
@@ -7,6 +7,7 @@
import { supabase } from '$lib/utils/functions/supabase';
import { currentOrg, createQuizModal, currentOrgPath, quizesStore } from '$lib/utils/store/org';
import { createQuizValidation } from '$lib/utils/functions/validator';
import { t } from '$lib/utils/functions/translations';
let open = false;
let errors = {
@@ -97,21 +98,23 @@
</script>
<svelte:head>
<title>Quizzes - ClassroomIO</title>
<title>{$t('components.quiz.title')} - ClassroomIO</title>
</svelte:head>
<Modal
onClose={handleClose}
bind:open
width="w-4/5 md:w-2/5"
modalHeading="{$createQuizModal.openEdit ? 'Update' : 'Create'} a Quiz"
modalHeading="{$createQuizModal.openEdit
? $t('components.quiz.update')
: $t('components.quiz.create')} {$t('components.quiz.a_quiz')}"
>
<form on:submit|preventDefault={createQuiz}>
<TextField
label="Quiz Title"
label={$t('components.quiz.quiz_title')}
bind:value={$createQuizModal.title}
autofocus={true}
placeholder="Your quiz title"
placeholder={$t('components.quiz.placehoolder')}
className="mb-4"
isRequired={true}
errorMessage={errors.title}
@@ -121,7 +124,9 @@
<div class="mt-5 flex items-center justify-end">
<PrimaryButton
className="px-6 py-3"
label={$createQuizModal.openEdit ? 'Save Changes' : 'Continue'}
label={$createQuizModal.openEdit
? $t('components.quiz.save')
: $t('components.quiz.continue')}
type="submit"
isDisabled={isLoading}
{isLoading}
@@ -9,6 +9,7 @@
import { onRename, onDelete } from '$lib/utils/services/org/quiz';
import { themeImages } from '$lib/utils/constants/quiz';
import { calDateDiff } from '$lib/utils/functions/date';
import { t } from '$lib/utils/functions/translations';
export let quiz;
export let totalQuestions;
@@ -28,7 +29,7 @@
// onClick: onReport(quiz),
// },
{
label: 'Rename',
label: $t('components.quiz.rename'),
onClick() {
onRename(quiz);
}
@@ -40,7 +41,7 @@
// },
// },
{
label: 'Delete',
label: $t('components.quiz.delete'),
onClick() {
onDelete(quiz);
}
@@ -77,13 +78,13 @@
<div class="flex md:flex-row flex-col justify-between">
<div>
<p class="mb-2">{pluralize('question', totalQuestions, true)}</p>
<p class="mb-2 md:mb-0">Updated {calDateDiff(quiz.updated_at)}</p>
<p class="mb-2 md:mb-0">{$t('components.quiz.updated')} {calDateDiff(quiz.updated_at)}</p>
</div>
<PrimaryButton
className="px-6 py-3"
variant={VARIANTS.OUTLINED}
label="Start Quiz"
label={$t('components.quiz.start')}
onClick={startQuiz}
/>
</div>
@@ -4,6 +4,7 @@
import { optionImage } from '$lib/utils/constants/quiz';
import TextField from '$lib/components/Form/TextField.svelte';
import IconButton from '$lib/components/IconButton/index.svelte';
import { t } from '$lib/utils/functions/translations';
export let currentQuestion = {
label: ''
@@ -17,10 +18,10 @@
<h1 class="text-white mb-5 font-bold">{currentQuestion.label}</h1>
{:else}
<TextField
placeholder="Start typing your question"
placeholder={$t('components.quiz.start_typing')}
bind:value={currentQuestion.label}
className="w-full my-4"
errorMessage={currentError.isLabelEmpty && 'This field is required'}
errorMessage={currentError.isLabelEmpty && $t('components.quiz.required_field')}
isRequired={true}
bgColor="bg-white"
/>
@@ -37,11 +38,12 @@
{:else}
<TextField
bind:value={option.label}
placeholder="Type answer"
placeholder={$t('components.quiz.type_answer')}
bgColor="bg-white"
className="mt-3"
isDisabled={currentQuestion.type === 'boolean'}
errorMessage={optionHasError(option.id, currentError.options) && 'Please enter a label'}
errorMessage={optionHasError(option.id, currentError.options) &&
$t('components.quiz.label')}
/>
<div class="flex justify-end absolute top-2 right-2">
<IconButton
@@ -7,6 +7,7 @@
import { snackbar } from '$lib/components/Snackbar/store';
import { supabase } from '$lib/utils/functions/supabase';
import { currentOrg, quizesStore } from '$lib/utils/store/org';
import { t } from '$lib/utils/functions/translations';
let isLoading = false;
@@ -43,9 +44,9 @@
{:else}
<Box>
<CoursesEmptyIcon />
<h3 class="dark:text-white text-2xl my-5">No Quiz Created</h3>
<h3 class="dark:text-white text-2xl my-5">{$t('components.quiz.no_quizz')}</h3>
<p class="dark:text-white w-1/3 text-center">
Create interactive quizzes with scoreboard for your students.
{$t('components.quiz.interactive')}
</p>
</Box>
{/each}
@@ -4,6 +4,7 @@
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
import { currentOrgPlan, isFreePlan } from '$lib/utils/store/org';
import SectionTitle from '../SectionTitle.svelte';
import { t } from '$lib/utils/functions/translations';
let isRedirecting = false;
@@ -26,16 +27,18 @@
<Grid class="border-c rounded border-gray-200 dark:border-neutral-600 w-full mt-5">
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
<Column sm={4} md={4} lg={4}><SectionTitle>Billing details</SectionTitle></Column>
<Column sm={4} md={4} lg={4}
><SectionTitle>{$t('settings.billing.sub_title')}</SectionTitle></Column
>
<Column sm={8} md={8} lg={8}>
<h4 class="dark:text-white lg:mt-0">Manage your billing</h4>
<h4 class="dark:text-white lg:mt-0">{$t('settings.billing.manage')}</h4>
{#if $isFreePlan}
You don't have any active billing
{$t('settings.billing.active')}
{:else}
<p class="mb-3">We use Lemonsqueezy to help manage your billing</p>
<p class="mb-3">{$t('settings.billing.lemonsqueezy')}</p>
<PrimaryButton
label="Open billing"
label={$t('settings.billing.open_billing')}
variant={VARIANTS.CONTAINED_DARK}
onClick={onOpenBilling}
isLoading={isRedirecting}
@@ -12,6 +12,7 @@
import SectionTitle from '../SectionTitle.svelte';
import VisitOrgSiteButton from '$lib/components/Buttons/VisitOrgSite.svelte';
import { updateOrgSiteNameValidation } from '$lib/utils/functions/validator';
import { t } from '$lib/utils/functions/translations';
type Error = {
siteName: string;
@@ -45,7 +46,7 @@
console.log('Updating organisation', org);
if (error) {
console.log('Error: create organisation', error);
errors.siteName = 'Sitename already exists.';
errors.siteName = $t('add_org.sitename');
} else {
snackbar.success();
$currentOrg.siteName = siteName;
@@ -74,10 +75,12 @@
<Grid class="border rounded border-gray-200 dark:border-neutral-600 w-full mt-5">
<Row class="py-7 border-bottom-c">
<Column sm={2} md={2} lg={4} class="text-lg"><SectionTitle>Add</SectionTitle></Column>
<Column sm={2} md={2} lg={4} class="text-lg"
><SectionTitle>{$t('components.settings.domains.add')}</SectionTitle></Column
>
<Column sm={2} md={6} lg={8}>
<p class="text-md text-gray-500 dark:text-white mb-5">
Add your team mates or collaborators to your organization. Start working together
{$t('components.settings.domains.work_together')}
</p>
<div>
@@ -94,7 +97,7 @@
/>
<div class="flex items-center mb-6">
<PrimaryButton
label="Update Domain"
label={$t('components.settings.domains.update')}
className="py-4"
variant={VARIANTS.OUTLINED}
onClick={handleChangeDomain}
@@ -108,10 +111,12 @@
</Row>
<Row class="py-7 border-bottom-c">
<Column sm={2} md={2} lg={4} class="text-lg"><SectionTitle>Custom domain</SectionTitle></Column>
<Column sm={2} md={2} lg={4} class="text-lg"
><SectionTitle>{$t('components.settings.domains.custom')}</SectionTitle></Column
>
<Column sm={2} md={6} lg={8}>
<div class="flex items-center">
<p class="dark:text-white mr-3">Add your own domain name</p>
<p class="dark:text-white mr-3">{$t('components.settings.domains.domain')}</p>
<ComingSoon />
</div>
</Column>
@@ -9,6 +9,7 @@
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
import SectionTitle from '../SectionTitle.svelte';
import ArrowUpRightIcon from '$lib/components/Icons/ArrowTopRight.svelte';
import { t } from '$lib/utils/functions/translations';
let chatId: number | null;
@@ -24,11 +25,11 @@
if (error) {
console.log(error);
snackbar.error('Integration Failed, please try again later');
snackbar.error('snackbar.course_settings.error.failed_integration');
} else {
$profile.telegram_chat_id = chatId;
chatId = null;
snackbar.success('Integration successful');
snackbar.success('snackbar.course_settings.success.successful_integration');
}
}
@@ -39,11 +40,11 @@
.match({ id: $profile.id });
if (error) {
snackbar.error('Deletion Failed, please try again later');
snackbar.error('snackbar.course_settings.error.failed_deletion');
console.log(error);
} else {
$profile.telegram_chat_id = null;
snackbar.success('Integration deleted');
snackbar.success('snackbar.course_settings.success.successful_deletion');
}
}
</script>
@@ -52,7 +53,7 @@
<Row class="flex lg:flex-row flex-col justify-center lg:justify-start py-7 border-bottom-c">
<Column sm={4} md={4} lg={4} class="flex items-center justify-center">
<img src="/telegram-svg.svg" alt="" class="w-10 mr-2" />
<SectionTitle>Telegram</SectionTitle></Column
<SectionTitle>{$t('settings.integrations.heading')}</SectionTitle></Column
>
<Column sm={8} md={8} lg={8} class="mt-2 lg:mt-0 flex flex-col items-center lg:items-start">
@@ -60,25 +61,26 @@
<div class="flex flex-col items-center w-full">
<div class="flex items-center">
<CheckmarkFilled size={32} class="mr-2" style="fill: green;" />
<SectionTitle>Integration Successful</SectionTitle>
<SectionTitle>{$t('settings.integrations.success_message')}</SectionTitle>
</div>
<PrimaryButton className="mt-3" variant={VARIANTS.CONTAINED_DANGER} onClick={deleteChatId}
>Disconnect</PrimaryButton
>{$t('settings.integrations.disconnect')}</PrimaryButton
>
</div>
{:else}
<div>
<h3 class="font-normal text-lg">
Connect your account to telegram to get notified of any change within the application.
{$t('settings.integrations.sub_heading')}
</h3>
<h4 class="font-normal">Step 1: Authenticate your account via the ClassroomIO bot.</h4>
<h4 class="font-normal">{$t('settings.integrations.step_1')}</h4>
<PrimaryButton
className="mb-5"
variant={VARIANTS.OUTLINED}
onClick={() => window.open('https://t.me/classroomio_bot', '_blank')}
><span class="mr-2">Open Bot</span> <ArrowUpRightIcon /></PrimaryButton
><span class="mr-2">{$t('settings.integrations.button_1')}</span>
<ArrowUpRightIcon /></PrimaryButton
>
<h4 class="font-normal">Step 2: Enter the Chat ID given to you by the bot.</h4>
<h4 class="font-normal">{$t('settings.integrations.step_2')}</h4>
<TextField
bind:value={chatId}
placeholder="360111"
@@ -86,7 +88,7 @@
type="number"
isRequired
/>
<PrimaryButton onClick={addChatId}>Connect</PrimaryButton>
<PrimaryButton onClick={addChatId}>{$t('settings.integrations.button_2')}</PrimaryButton>
</div>
{/if}
</Column>
@@ -16,13 +16,14 @@
import type { OrgLandingPageJson } from './store';
import UploadWidget from '$lib/components/UploadWidget/index.svelte';
import { handleOpenWidget } from '$lib/components/CourseLandingPage/store';
import { t } from '$lib/utils/functions/translations';
let isSaving = false;
let creatingNewQuestion = false;
let widgetKey = '';
const banner = [
{ value: 'video', label: 'Video' },
{ value: 'image', label: 'Image' }
{ value: 'video', label: `${$t('settings.landing_page.actions.banner_type.video')}` },
{ value: 'image', label: `${$t('settings.landing_page.actions.banner_type.image')}` }
];
const supabase = getSupabase();
@@ -91,8 +92,8 @@
.match({ id: $currentOrg.id });
if (error) {
const message = error?.message || 'Please try again';
snackbar.error(`Update failed: ${message}`);
const message = error?.message || 'snackbar.lms.error.try_again';
snackbar.error(`snackbar.lms.error.update ${message}`);
} else {
$currentOrg.landingpage = $landingPageSettings;
snackbar.success();
@@ -120,57 +121,61 @@
<Grid class="border-c rounded border-gray-200 dark:border-neutral-600 w-full mt-5 relative">
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
<Column sm={4} md={4} lg={4}>
<SectionTitle>Landing Page</SectionTitle>
<SectionTitle>{$t('settings.landing_page.heading')}</SectionTitle>
<Toggle bind:toggled={$landingPageSettings.header.show} size="sm">
<span slot="labelA" style="color: gray">Hide section</span>
<span slot="labelB" style="color: gray">Show section</span>
<span slot="labelA" style="color: gray">{$t('settings.landing_page.hide_section')}</span>
<span slot="labelB" style="color: gray">{$t('settings.landing_page.show_section')}</span>
</Toggle>
</Column>
<Column sm={8} md={8} lg={8} class="mt-4 lg:mt-0">
<TextField
label="Title"
placeholder="Write your title here"
label={$t('settings.landing_page.title')}
placeholder={$t('course.navItem.lessons.exercises.all_exercises.write_your_title')}
bind:value={$landingPageSettings.header.title}
className="w-full mb-5"
/>
<TextField
label="Title-Highlight"
placeholder="Write your titlehighlight here"
label={$t('settings.landing_page.title_highlight')}
placeholder={$t('course.navItem.lessons.exercises.all_exercises.highlight')}
className="w-full mb-5"
bind:value={$landingPageSettings.header.titleHighlight}
/>
<TextArea
label="Subtitle"
label={$t('settings.landing_page.subtitle')}
labelClassName="font-light"
placeholder="Write your subtitle here"
placeholder={$t('course.navItem.lessons.exercises.all_exercises.write_your_subtitle')}
className="w-full mb-5"
bind:value={$landingPageSettings.header.subtitle}
isAIEnabled={true}
/>
<SectionTitle>Actions</SectionTitle>
<SectionTitle>{$t('settings.landing_page.actions.heading')}</SectionTitle>
<TextField
label="Label"
placeholder="Write your label here"
label={$t('settings.landing_page.actions.label')}
placeholder={$t('course.navItem.lessons.exercises.all_exercises.label')}
className="w-full mt-3 mb-5"
bind:value={$landingPageSettings.header.action.label}
/>
<div class="gap-2 mb-5">
<TextField
label="Link"
placeholder="Write your link here"
label={$t('settings.landing_page.actions.link')}
placeholder={$t('course.navItem.lessons.exercises.all_exercises.link')}
bind:value={$landingPageSettings.header.action.link}
/>
<Toggle bind:toggled={$landingPageSettings.header.action.redirect} size="sm">
<span slot="labelA" style="color: gray">No redirect</span>
<span slot="labelB" style="color: gray">Redirect</span>
<span slot="labelA" style="color: gray"
>{$t('settings.landing_page.actions.no_redirect')}</span
>
<span slot="labelB" style="color: gray"
>{$t('settings.landing_page.actions.redirect')}</span
>
</Toggle>
</div>
<div />
<RadioButtonGroup
legendText="Banner Type"
legendText={$t('settings.landing_page.actions.banner_type.heading')}
bind:selected={$landingPageSettings.header.banner.type}
class="mt-10 mb-5"
>
@@ -181,15 +186,15 @@
{#if $landingPageSettings.header.banner.type === 'video'}
<div class="gap-2 mt-3 mb-5">
<TextField
label="Link"
placeholder="Write your video link here"
label={$t('settings.landing_page.actions.link')}
placeholder={$t('course.navItem.lessons.exercises.all_exercises.video')}
bind:value={$landingPageSettings.header.banner.video}
/>
</div>
{:else}
<PrimaryButton
variant={VARIANTS.OUTLINED}
label="Select Image"
label={$t('settings.landing_page.about.select_image')}
className="mt-3"
onClick={() => widgetControl('banner')}
/>
@@ -203,8 +208,12 @@
{/if}
<Toggle bind:toggled={$landingPageSettings.header.banner.show} size="sm">
<span slot="labelA" style="color: gray">Hide banner</span>
<span slot="labelB" style="color: gray">Show banner</span>
<span slot="labelA" style="color: gray"
>{$t('settings.landing_page.actions.hide_banner')}</span
>
<span slot="labelB" style="color: gray"
>{$t('settings.landing_page.actions.show_banner')}</span
>
</Toggle>
{#if $handleOpenWidget.open && widgetKey === 'banner'}
<UploadWidget bind:imageURL={$landingPageSettings.header.banner.image} />
@@ -214,34 +223,34 @@
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
<Column sm={4} md={4} lg={4}
><SectionTitle>About</SectionTitle>
><SectionTitle>{$t('settings.landing_page.about.heading')}</SectionTitle>
<Toggle bind:toggled={$landingPageSettings.aboutUs.show} size="sm">
<span slot="labelA" style="color: gray">Hide section</span>
<span slot="labelB" style="color: gray">Show section</span>
<span slot="labelA" style="color: gray">{$t('settings.landing_page.hide_section')}</span>
<span slot="labelB" style="color: gray">{$t('settings.landing_page.show_section')}</span>
</Toggle></Column
>
<Column sm={8} md={8} lg={8} class="mt-4 lg:mt-0">
<TextField
label="Title"
placeholder="Write your title here"
label={$t('settings.landing_page.about.title')}
placeholder={$t('course.navItem.lessons.exercises.all_exercises.write_your_title')}
bind:value={$landingPageSettings.aboutUs.title}
className="mb-5"
/>
<TextArea
label="Content"
label={$t('settings.landing_page.about.content')}
labelClassName="font-light"
placeholder="Write your Content here"
placeholder={$t('course.navItem.lessons.exercises.all_exercises.content')}
className="mb-5"
bind:value={$landingPageSettings.aboutUs.content}
isAIEnabled={true}
/>
<div>
<p class="font-bold">Upload an image</p>
<p class="font-bold">{$t('settings.landing_page.about.upload_an_image')}</p>
<PrimaryButton
variant={VARIANTS.OUTLINED}
label="Select Image"
label={$t('settings.landing_page.about.select_image')}
className="mt-3"
onClick={() => widgetControl('about-us')}
/>
@@ -261,27 +270,31 @@
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
<Column sm={4} md={4} lg={4}
><SectionTitle>Courses</SectionTitle>
><SectionTitle>{$t('settings.landing_page.courses.heading')}</SectionTitle>
<Toggle bind:toggled={$landingPageSettings.courses.show} size="sm">
<span slot="labelA" style="color: gray">Hide section</span>
<span slot="labelB" style="color: gray">Show section</span>
<span slot="labelA" style="color: gray"
>{$t('settings.landing_page.courses.hide_section')}</span
>
<span slot="labelB" style="color: gray"
>{$t('settings.landing_page.courses.show_section')}</span
>
</Toggle>
</Column>
<Column sm={8} md={8} lg={8} class="mt-4 lg:mt-0">
<TextField
label="Title"
placeholder="Write your title here"
label={$t('settings.landing_page.courses.title')}
placeholder={$t('course.navItem.lessons.exercises.all_exercises.write_your_title')}
bind:value={$landingPageSettings.courses.title}
/>
<TextField
label="Title-Highlight"
placeholder="Write your title highlight here"
label={$t('settings.landing_page.courses.title_highlight')}
placeholder={$t('course.navItem.lessons.exercises.all_exercises.highlight')}
bind:value={$landingPageSettings.courses.titleHighlight}
/>
<TextArea
label="Subtitle"
label={$t('settings.landing_page.courses.subtitle')}
labelClassName="font-light"
placeholder="Write your subtitle here"
placeholder={$t('course.navItem.lessons.exercises.all_exercises.write_your_subtitle')}
bind:value={$landingPageSettings.courses.subtitle}
isAIEnabled={true}
/>
@@ -290,31 +303,37 @@
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
<Column sm={4} md={4} lg={4}
><SectionTitle>Frequently Asked Questions</SectionTitle>
><SectionTitle>{$t('settings.landing_page.faq.heading')}</SectionTitle>
<Toggle bind:toggled={$landingPageSettings.faq.show} size="sm">
<span slot="labelA" style="color: gray">Hide section</span>
<span slot="labelB" style="color: gray">Show section</span>
<span slot="labelA" style="color: gray">{$t('settings.landing_page.faq.hide_section')}</span
>
<span slot="labelB" style="color: gray">{$t('settings.landing_page.faq.show_section')}</span
>
</Toggle>
</Column>
<Column sm={8} md={8} lg={8}>
<TextField
label="Title"
placeholder="Write your title here"
label={$t('settings.landing_page.faq.title')}
placeholder={$t('course.navItem.lessons.exercises.all_exercises.write_your_title')}
bind:value={$landingPageSettings.faq.title}
className="mb-5"
/>
{#each $landingPageSettings.faq.questions as item (item.id)}
<div class="mb-3">
<TextField
label="Question"
placeholder="Write your question here"
label={$t('settings.landing_page.faq.question')}
placeholder={$t(
'course.navItem.lessons.exercises.all_exercises.write_your_question_here'
)}
bind:value={item.title}
className="mb-5"
/>
<TextArea
label="Answer"
label={$t('settings.landing_page.faq.answer')}
labelClassName="font-light"
placeholder="Write your answer here"
placeholder={$t(
'course.navItem.lessons.exercises.all_exercises.write_your_answer_here'
)}
bind:value={item.content}
className="mb-5"
isAIEnabled={true}
@@ -326,68 +345,86 @@
{/each}
{#if creatingNewQuestion}
<TextField
label="Question"
placeholder="Write your question here"
label={$t('settings.landing_page.faq.question')}
placeholder={$t(
'course.navItem.lessons.exercises.all_exercises.write_your_question_here'
)}
bind:value={newQuestion.title}
className="mb-5"
/>
<TextArea
label="Answer"
label={$t('settings.landing_page.faq.answer')}
labelClassName="font-light"
placeholder="Write your answer here"
placeholder={$t('course.navItem.lessons.exercises.all_exercises.write_your_answer_here')}
bind:value={newQuestion.content}
className="mb-5"
isAIEnabled={true}
/>
<div class="flex items-center gap-2">
<PrimaryButton variant={VARIANTS.OUTLINED} label="Save" onClick={saveNewFAQ} />
<PrimaryButton variant={VARIANTS.OUTLINED} label="Cancel" onClick={cancelNewFAQ} />
<PrimaryButton
variant={VARIANTS.OUTLINED}
label={$t('settings.landing_page.faq.save')}
onClick={saveNewFAQ}
/>
<PrimaryButton
variant={VARIANTS.OUTLINED}
label={$t('settings.landing_page.faq.cancel')}
onClick={cancelNewFAQ}
/>
</div>
{:else}
<PrimaryButton variant={VARIANTS.OUTLINED} label="Add New Faq" onClick={createNewFaq} />
<PrimaryButton
variant={VARIANTS.OUTLINED}
label={$t('settings.landing_page.faq.button')}
onClick={createNewFaq}
/>
{/if}
</Column>
</Row>
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
<Column sm={4} md={4} lg={4}
><SectionTitle>Contact us</SectionTitle>
><SectionTitle>{$t('settings.landing_page.contact_us.heading')}</SectionTitle>
<Toggle bind:toggled={$landingPageSettings.contact.show} size="sm">
<span slot="labelA" style="color: gray">Hide section</span>
<span slot="labelB" style="color: gray">Show section</span>
<span slot="labelA" style="color: gray"
>{$t('settings.landing_page.contact_us.hide_section')}</span
>
<span slot="labelB" style="color: gray"
>{$t('settings.landing_page.contact_us.show_section')}</span
>
</Toggle>
</Column>
<Column sm={8} md={8} lg={8}>
<TextField
label="Title"
placeholder="Write your title here"
label={$t('settings.landing_page.contact_us.title')}
placeholder={$t('course.navItem.lessons.exercises.all_exercises.write_your_title')}
bind:value={$landingPageSettings.contact.title}
className="mb-5"
/>
<TextField
label="Title-highlight"
label={$t('settings.landing_page.contact_us.title_highlight')}
labelClassName="font-light"
placeholder="Write your title highlight here"
placeholder={$t('course.navItem.lessons.exercises.all_exercises.highlight')}
className="mb-5"
bind:value={$landingPageSettings.contact.titleHighlight}
/>
<TextArea
label="Subtitle"
label={$t('settings.landing_page.contact_us.subtitle')}
labelClassName="font-light"
placeholder="Write your subtitle here"
placeholder={$t('course.navItem.lessons.exercises.all_exercises.write_your_subtitle')}
className="mt-3 mb-5"
bind:value={$landingPageSettings.contact.subtitle}
isAIEnabled={true}
/>
<TextField
label="Phone number"
placeholder="Write your phone number here"
label={$t('settings.landing_page.contact_us.phone_number')}
placeholder={$t('course.navItem.lessons.exercises.all_exercises.number')}
className="mt-3 mb-5"
bind:value={$landingPageSettings.contact.phone}
/>
<TextField
label="Email"
placeholder="Write your email here"
label={$t('settings.landing_page.contact_us.email')}
placeholder={$t('course.navItem.lessons.exercises.all_exercises.email')}
className="mt-3 mb-5"
bind:value={$landingPageSettings.contact.email}
/>
@@ -396,31 +433,35 @@
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
<Column sm={4} md={4} lg={4}
><SectionTitle>Mailing List</SectionTitle>
><SectionTitle>{$t('settings.landing_page.mailing_list.heading')}</SectionTitle>
<Toggle bind:toggled={$landingPageSettings.mailinglist.show} size="sm">
<span slot="labelA" style="color: gray">Hide section</span>
<span slot="labelB" style="color: gray">Show section</span>
<span slot="labelA" style="color: gray"
>{$t('settings.landing_page.mailing_list.hide_section')}</span
>
<span slot="labelB" style="color: gray"
>{$t('settings.landing_page.mailing_list.show_section')}</span
>
</Toggle>
</Column>
<Column sm={8} md={8} lg={8} class="mt-4 lg:mt-0">
<TextField
label="Title"
placeholder="Write your title here"
label={$t('settings.landing_page.mailing_list.title')}
placeholder={$t('course.navItem.lessons.exercises.all_exercises.write_your_title')}
bind:value={$landingPageSettings.mailinglist.title}
className="mb-5"
/>
<TextArea
label="Subtitle"
label={$t('settings.landing_page.mailing_list.subtitle')}
labelClassName="font-light"
placeholder="Write your subtitle here"
placeholder={$t('course.navItem.lessons.exercises.all_exercises.write_your_subtitle')}
className="mb-5"
bind:value={$landingPageSettings.mailinglist.subtitle}
isAIEnabled={true}
/>
<TextField
label="Button-label"
placeholder="Write your label here"
label={$t('settings.landing_page.mailing_list.button_label')}
placeholder={$t('course.navItem.lessons.exercises.all_exercises.label')}
className="mb-5"
bind:value={$landingPageSettings.mailinglist.buttonLabel}
/>
@@ -429,16 +470,20 @@
<Row class="flex lg:flex-row flex-col py-7">
<Column sm={4} md={4} lg={4}
><SectionTitle>Footer</SectionTitle>
><SectionTitle>{$t('settings.landing_page.footer.heading')}</SectionTitle>
<Toggle bind:toggled={$landingPageSettings.footer.show} size="sm">
<span slot="labelA" style="color: gray">Hide section</span>
<span slot="labelB" style="color: gray">Show section</span>
<span slot="labelA" style="color: gray"
>{$t('settings.landing_page.footer.hide_section')}</span
>
<span slot="labelB" style="color: gray"
>{$t('settings.landing_page.footer.show_section')}</span
>
</Toggle>
</Column>
<Column sm={8} md={8} lg={8} class="mt-4 lg:mt-0">
<TextField
label="Facebook"
placeholder="Write your Facebook link here"
label={$t('settings.landing_page.footer.facebook')}
placeholder={$t('course.navItem.lessons.exercises.all_exercises.facebook')}
bind:value={$landingPageSettings.footer.facebook}
className="mb-5"
/>
@@ -449,14 +494,14 @@
bind:value={$landingPageSettings.footer.instagram}
/> -->
<TextField
label="Twitter"
placeholder="Write your Twitter link here"
label={$t('settings.landing_page.footer.twitter')}
placeholder={$t('course.navItem.lessons.exercises.all_exercises.twitter')}
className="mb-5"
bind:value={$landingPageSettings.footer.twitter}
/>
<TextField
label="Linkedin"
placeholder="Write your Linkedin link here"
label={$t('settings.landing_page.footer.linkedin')}
placeholder={$t('course.navItem.lessons.exercises.all_exercises.linkedin')}
className="mb-5"
bind:value={$landingPageSettings.footer.linkedin}
/>
@@ -464,7 +509,7 @@
</Row>
<div class="sticky desktop float-right bottom-12 mr-2 z-[120]">
<PrimaryButton
label="Save Changes"
label={$t('settings.landing_page.save_changes')}
isLoading={isSaving}
isDisabled={isSaving}
onClick={handleSave}
@@ -0,0 +1,28 @@
<script>
import { Dropdown } from 'carbon-components-svelte';
import { t, initialized } from '$lib/utils/functions/translations';
import { LANGUAGES } from '$lib/utils/constants/translation';
import { LOCALE } from '$lib/utils/types';
export let className = '';
export let value = LOCALE.EN;
export let hasLangChanged = false;
const dropdownItems = [{ id: '', text: 'Pick a Language' }, ...LANGUAGES];
function handleSelect(event) {
value = event.detail.selectedId;
hasLangChanged = true;
}
</script>
{#if $initialized}
<Dropdown
titleText={$t('content.toggle_label')}
items={dropdownItems}
selectedId={value}
on:select={handleSelect}
class="h-full {className}"
/>
{/if}
@@ -10,6 +10,7 @@
import { snackbar } from '$lib/components/Snackbar/store';
import { currentOrg, currentOrgPath } from '$lib/utils/store/org';
import SectionTitle from '../SectionTitle.svelte';
import { t } from '$lib/utils/functions/translations';
import { isFreePlan } from '$lib/utils/store/org';
let avatar;
@@ -75,16 +76,16 @@
...updates
}));
snackbar.success('Update successful');
snackbar.success('snackbar.course_settings.success.update_successful');
if (error) throw error;
} catch (error) {
let message = error.message;
if (message.includes('profile_username_key')) {
message = 'username already exists';
message = $t('snackbar.lms.error.username_exists');
}
snackbar.error(`Update failed: ${message}`);
snackbar.error(`${$t('snackbar.lms.error.update')} ${message}`);
loading = false;
} finally {
loading = false;
@@ -98,10 +99,13 @@
<Grid class="border-c rounded border-gray-200 dark:border-neutral-600 w-full mt-5">
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
<Column sm={4} md={4} lg={4}><SectionTitle>Organization Profile</SectionTitle></Column>
<Column sm={4} md={4} lg={4}
><SectionTitle>{$t('settings.organization.organization_profile.heading')}</SectionTitle
></Column
>
<Column sm={8} md={8} lg={8} class="mt-2 lg:mt-0 flex flex-col items-center lg:items-start">
<TextField
label="Organization Name"
label={$t('settings.organization.organization_profile.organization_name')}
bind:value={$currentOrg.name}
className="w-full lg:w-60 mb-5"
/>
@@ -112,7 +116,7 @@
widthHeight="w-24 h-24"
/>
<PrimaryButton
label="Update Organization"
label={$t('settings.organization.organization_profile.update_organization')}
className="px-6 py-3 lg:mr-5 mt-5"
isLoading={loading}
isDisabled={loading}
@@ -121,9 +125,14 @@
</Column>
</Row>
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
<Column sm={4} md={4} lg={4}><SectionTitle>Theme</SectionTitle></Column>
<Column sm={4} md={4} lg={4}
><SectionTitle>{$t('settings.organization.organization_profile.theme.heading')}</SectionTitle
></Column
>
<Column sm={8} md={8} lg={8}>
<h4 class="dark:text-white lg:mt-0">Set your brand color</h4>
<h4 class="dark:text-white lg:mt-0">
{$t('settings.organization.organization_profile.theme.sub_heading')}
</h4>
<div class="flex gap-2">
<button
@@ -169,14 +178,20 @@
</Column>
</Row>
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
<Column sm={4} md={4} lg={4}><SectionTitle>Custom Domain</SectionTitle></Column>
<Column sm={4} md={4} lg={4}
><SectionTitle
>{$t('settings.organization.organization_profile.custom_domain.heading')}</SectionTitle
></Column
>
<Column sm={8} md={8} lg={8}>
<h4 class="dark:text-white lg:mt-0">Customise your Domain</h4>
<h4 class="dark:text-white lg:mt-0">
{$t('settings.organization.organization_profile.custom_domain.sub_heading')}
</h4>
<p class="text-sm text-gray-500 dark:text-white">
Create a custom URL so your audience can get to your organization easily
{$t('settings.organization.organization_profile.custom_domain.body')}
</p>
<PrimaryButton
label="Edit domain"
label={$t('settings.organization.organization_profile.custom_domain.button')}
className="my-7 py-5 px-10"
variant={VARIANTS.OUTLINED}
onClick={() => gotoSetting('/domains')}
@@ -184,11 +199,16 @@
</Column>
</Row>
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
<Column sm={4} md={4} lg={4}><SectionTitle>Team</SectionTitle></Column>
<Column sm={4} md={4} lg={4}
><SectionTitle>{$t('settings.organization.organization_profile.team.heading')}</SectionTitle
></Column
>
<Column sm={8} md={8} lg={8}>
<h4 class="dark:text-white lg:mt-0">Set up your website</h4>
<h4 class="dark:text-white lg:mt-0">
{$t('settings.organization.organization_profile.team.sub_heading')}
</h4>
<p class="text-sm text-gray-500 dark:text-white">
Add team mates to your organization so you can both collaborate on projects.
{$t('settings.organization.organization_profile.team.body')}
</p>
<PrimaryButton
className="my-7 py-5 px-10 flex items-center gap-2 justify-center"
@@ -10,18 +10,25 @@
import { snackbar } from '$lib/components/Snackbar/store';
import LogoutButton from '$lib/components/Buttons/Logout/index.svelte';
import generateUUID from '$lib/utils/functions/generateUUID';
import { t } from '$lib/utils/functions/translations';
import LanguagePicker from './LanguagePicker.svelte';
import { handleLocaleChange } from '$lib/utils/functions/translations';
let avatar = '';
let loading = false;
let hasLangChanged = false;
let locale = '';
async function handleUpdate() {
try {
console.log({ hasLangChanged });
loading = true;
const updates = {
fullname: $profile.fullname,
username: $profile.username,
email: $profile.email
email: $profile.email,
locale
};
if (avatar) {
@@ -46,26 +53,32 @@
..._profile,
...updates
}));
snackbar.success('Update successful');
snackbar.success('snackbar.course_settings.success.update_successful');
if (hasLangChanged) {
handleLocaleChange(locale);
}
if (error) throw error;
} catch (error) {
let message = error.message;
if (message.includes('profile_username_key')) {
message = 'username already exists';
message = $t('snackbar.lms.error.username_exists');
}
snackbar.success(`Update failed: ${message}`);
snackbar.error(`${$t('snackbar.lms.error.update')} ${message}`);
loading = false;
} finally {
loading = false;
}
}
$: locale = !locale ? $profile.locale : locale;
</script>
<Grid class="border-c rounded border-gray-200 dark:border-neutral-600 w-full mt-5">
<Row class="flex flex-col lg:flex-row items-center lg:items-start py-7 border-bottom-c ">
<Column sm={4} md={8} lg={4} class="mt-2 md:mt-0">
<SectionTitle>Profile Picture</SectionTitle>
<SectionTitle>{$t('settings.profile.profile_picture.heading')}</SectionTitle>
</Column>
<Column sm={2} md={4} lg={8} class="mt-2 lg:mt-0">
<UploadImage bind:avatar src={$profile.avatar_url} widthHeight="w-16 h-16 lg:w-24 lg:h-24" />
@@ -73,17 +86,31 @@
</Row>
<Row class="flex flex-col lg:flex-row py-7 border-bottom-c">
<Column sm={4} md={4} lg={4}>
<SectionTitle>Personal Information</SectionTitle>
<SectionTitle>{$t('settings.profile.personal_information.heading')}</SectionTitle>
</Column>
<Column sm={8} md={8} lg={8} class="mt-2 lg:mt-0">
<TextField label="Full Name" bind:value={$profile.fullname} className="w-full lg:w-60 mb-4" />
<TextField label="Username" bind:value={$profile.username} className="w-full lg:w-60 mb-4" />
<TextField label="Email" bind:value={$profile.email} className="w-full lg:w-60 mb-4" />
<TextField
label={$t('settings.profile.personal_information.full_name')}
bind:value={$profile.fullname}
className="w-full lg:w-60 mb-4"
/>
<TextField
label={$t('settings.profile.personal_information.username')}
bind:value={$profile.username}
className="w-full lg:w-60 mb-4"
/>
<TextField
label={$t('settings.profile.personal_information.email')}
bind:value={$profile.email}
className="w-full lg:w-60 mb-4"
/>
<LanguagePicker bind:hasLangChanged bind:value={locale} className="w-full lg:w-60 mb-4" />
</Column>
</Row>
<Row class="m-5 w-full flex items-center gap-2 lg:justify-center">
<PrimaryButton
label="Update Profile"
label={$t('settings.profile.update_profile')}
variant={VARIANTS.CONTAINED_DARK}
className="mr-5"
isLoading={loading}
@@ -46,7 +46,7 @@
);
if (doesEmailExist) {
snackbar.error('User Already Exists');
snackbar.error('snackbar.team_members.user_exists');
isLoading = false;
return;
}
@@ -65,7 +65,7 @@
apiError = `${error}`;
console.error('onSendInvite:', error);
snackbar.error(`Failed to send invite, please try again`);
snackbar.error(`snackbar.team_members.invite_fail`);
isLoading = false;
return;
}
@@ -95,7 +95,7 @@
const isLast = index === emails.length - 1;
if (isLast) {
snackbar.success('Invite Sent!');
snackbar.success('snackbar.team_members.invite_sent');
emailsStr = '';
isLoading = false;
@@ -110,7 +110,7 @@
if (error) {
console.error('onRemove:', error);
snackbar.error('Failed to remove user, please try again');
snackbar.error('snackbar.team_members.remove_fail');
} else {
orgTeam.update((team) => [...team.filter((member) => member.id !== id)]);
}
@@ -11,6 +11,7 @@
import { goto } from '$app/navigation';
import { isOrgAdmin } from '$lib/utils/store/org';
import Integrations from './Integrations.svelte';
import { t } from '$lib/utils/functions/translations';
let selected = 0;
let query = new URLSearchParams($page.url.search);
@@ -44,35 +45,35 @@
tabs = [
{
key: 0,
label: 'Profile',
label: $t('settings.tabs.1'),
tabKey: '',
href: $page.url.pathname,
disabled: false
},
{
key: 1,
label: 'Organization',
label: $t('settings.tabs.2'),
tabKey: 'org',
href: `${$page.url.pathname}?tab=org`,
disabled: !$isOrgAdmin
},
{
key: 2,
label: 'LandingPage',
label: $t('settings.tabs.3'),
tabKey: 'landingpage',
href: `${$page.url.pathname}?tab=landingpage`,
disabled: !$isOrgAdmin
},
{
key: 3,
label: 'Billing',
label: $t('settings.tabs.4'),
tabKey: 'billing',
href: `${$page.url.pathname}?tab=billing`,
disabled: !$isOrgAdmin
},
{
key: 4,
label: 'Integrations',
label: $t('settings.tabs.5'),
tabKey: 'integrations',
href: `${$page.url.pathname}?tab=integrations`,
disabled: false
@@ -16,13 +16,16 @@
import { profile } from '$lib/utils/store/user';
import { NavClasses } from '$lib/utils/constants/reusableClass';
import { sideBar } from './store';
import { t } from '$lib/utils/functions/translations';
import { goto } from '$app/navigation';
let menuItems: {
path: string;
interface menuItems {
label: string;
path: string;
show: boolean;
}[] = [];
}
let menuItems: menuItems[] = [];
function isActive(pagePath: string, itemPath: string) {
const pageLinkItems = pagePath.split('/');
@@ -42,43 +45,70 @@
goto(window.location.pathname + '?upgrade=true');
};
$: {
menuItems = [
{
path: '',
label: 'Dashboard',
show: true
},
// {
// path: '/quiz',
// label: 'Quizzes'
// },
{
path: '/courses',
label: 'Courses',
show: true
},
{
path: '/community',
label: 'Community',
show: true
},
// {
// path: '/site',
// label: 'Site settings',
// },
{
path: '/audience',
label: 'Audience',
show: true
},
{
path: '/setup',
label: 'Setup',
show: $isOrgAdmin
}
];
}
// $: {
// menuItems = [
// {
// path: '',
// label: 'Dashboard',
// show: true
// },
// // {
// // path: '/quiz',
// // label: 'Quizzes'
// // },
// {
// path: '/courses',
// label: 'Courses',
// show: true
// },
// {
// path: '/community',
// label: 'Community',
// show: true
// },
// // {
// // path: '/site',
// // label: 'Site settings',
// // },
// {
// path: '/audience',
// label: 'Audience',
// show: true
// },
// {
// path: '/setup',
// label: 'Setup',
// show: $isOrgAdmin
// }
// ];
// }
$: menuItems = [
{
path: '',
label: $t('org_navigation.dashboard'),
show: true
},
{
path: '/courses',
label: $t('org_navigation.courses'),
show: true
},
{
path: '/community',
label: $t('org_navigation.community'),
show: true
},
{
path: '/audience',
label: $t('org_navigation.audience'),
show: true
},
{
path: '/setup',
label: $t('org_navigation.setup'),
show: $isOrgAdmin
}
];
</script>
<aside
@@ -138,10 +168,14 @@
>
<img src="/upgrade.png" alt="upgrade" class="h-16 w-16" />
<span class="flex flex-col gap-1">
<p class="text-base font-semibold">Become an Early Adopter</p>
<p class="text-xs">Unlock unlimited features and invest in our future</p>
<p class="text-base font-semibold">{$t('org_navigation.early_adopter')}</p>
<p class="text-xs">{$t('org_navigation.unlock')}</p>
</span>
<PrimaryButton label="Upgrade Now" onClick={openModal} className="font-normal" />
<PrimaryButton
label={$t('org_navigation.upgrade')}
onClick={openModal}
className="font-normal"
/>
</div>
{/if}
@@ -149,7 +183,7 @@
<a href={$currentOrgPath} class="text-black no-underline" on:click={toggleSidebar}>
<li class="mb-2 flex items-center rounded px-2.5 py-1.5">
<HelpIcon size={20} class="carbon-icon dark:text-white" />
<p class="ml-2.5 dark:text-white text-sm font-medium">Help</p>
<p class="ml-2.5 dark:text-white text-sm font-medium">{$t('org_navigation.help')}</p>
</li>
</a>
<a href="{$currentOrgPath}/settings" class="text-black no-underline" on:click={toggleSidebar}>
@@ -167,7 +201,7 @@
width="w-[1.2rem]"
height="h-[1.2rem]"
/>
<p class="ml-2.5 text-sm font-medium">Settings</p>
<p class="ml-2.5 text-sm font-medium">{$t('org_navigation.settings')}</p>
</li>
</a>
</ul>
@@ -8,6 +8,7 @@
NOTIFICATION_NAME
} from '$lib/utils/services/notification/notification';
import { currentOrg } from '$lib/utils/store/org';
import { t } from '$lib/utils/functions/translations';
const WAIT_SEC = 120;
const WAIT_TIME = WAIT_SEC * 1000;
@@ -31,7 +32,7 @@
isSent = true;
} catch (error) {
snackbar.error('error while sending code');
snackbar.error('verify_email_modal.snackbar.error');
} finally {
loading = false;
}
@@ -53,9 +54,11 @@
<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">Verify your Email</p>
<p class="text-sm w-[70%] text-gray-500">
We've sent a verification email to {$profile.email} to confirm the validity of the email provided.
<p class="font-bold text-xl">{$t('verify_email_modal.heading')}</p>
<p class="text-sm w-[70%] text-gray-700">
{$t('verify_email_modal.sent_verification')}
{$profile.email}
{$t('verify_email_modal.to_confirm')}
</p>
<div class="flex items-center flex-col">
@@ -65,13 +68,17 @@
onClick={resendVerificationCode}
>
{#if loading}
Loading...
{$t('verify_email_modal.loading')}
{:else}
Resend Verification Code
{$t('verify_email_modal.resend')}
{/if}
</PrimaryButton>
{#if isSent}
<p class="text-xs text-gray-700">Resend verification in {countDown} seconds</p>
<p class="text-xs text-gray-700">
{$t('verify_email_modal.resend_in')}
{countDown}
{$t('verify_email_modal.seconds')}
</p>
{/if}
</div>
</div>