feat: support custom domain (#462)
* feat: add custom domain CRUD * chore: fetch organization in layout in server before render * debugging * update log
This commit is contained in:
@@ -30,4 +30,9 @@ PRIVATE_APP_SUBDOMAINS=app
|
||||
|
||||
LEMON_SQUEEZY_API_KEY=
|
||||
LEMON_SQUEEZY_STORE_ID=
|
||||
LEMON_SQUEEZY_WEBHOOK_SECRET=
|
||||
LEMON_SQUEEZY_WEBHOOK_SECRET=
|
||||
|
||||
## Custom Domain
|
||||
TEAM_ID_VERCEL=
|
||||
PROJECT_ID_VERCEL=
|
||||
AUTH_BEARER_TOKEN=
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
"form-data": "^4.0.0",
|
||||
"hotkeys-js": "^3.11.2",
|
||||
"html-to-image": "^1.11.11",
|
||||
"is-valid-domain": "^0.1.6",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jspdf": "^2.5.1",
|
||||
"jspdf-autotable": "^3.8.2",
|
||||
@@ -61,6 +62,7 @@
|
||||
"svelte-meta-tags": "^3.1.2",
|
||||
"svelte-ui": "workspace:*",
|
||||
"sveltekit-i18n": "^2.4.2",
|
||||
"tldts": "^6.1.41",
|
||||
"unsplash-js": "^7.0.18",
|
||||
"wait-on": "^7.0.1",
|
||||
"zod": "^3.21.4"
|
||||
|
||||
@@ -141,6 +141,29 @@ code {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.shake {
|
||||
animation: shake 0.85s;
|
||||
}
|
||||
@keyframes shake {
|
||||
10%,
|
||||
90% {
|
||||
transform: translate3d(-15px, 0, 0);
|
||||
}
|
||||
20%,
|
||||
80% {
|
||||
transform: translate3d(15px, 0, 0);
|
||||
}
|
||||
30%,
|
||||
50%,
|
||||
70% {
|
||||
transform: translate3d(-15px, 0, 0);
|
||||
}
|
||||
40%,
|
||||
60% {
|
||||
transform: translate3d(15px, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.bx--list-box__menu-item__option {
|
||||
white-space: normal !important;
|
||||
}
|
||||
|
||||
@@ -376,26 +376,4 @@
|
||||
.status-text {
|
||||
width: fit-content;
|
||||
}
|
||||
:global(.shake) {
|
||||
animation: shake 0.85s;
|
||||
}
|
||||
@keyframes shake {
|
||||
10%,
|
||||
90% {
|
||||
transform: translate3d(-15px, 0, 0);
|
||||
}
|
||||
20%,
|
||||
80% {
|
||||
transform: translate3d(15px, 0, 0);
|
||||
}
|
||||
30%,
|
||||
50%,
|
||||
70% {
|
||||
transform: translate3d(-15px, 0, 0);
|
||||
}
|
||||
40%,
|
||||
60% {
|
||||
transform: translate3d(15px, 0, 0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
{#if errorMessage}
|
||||
<p class="text-sm text-red-500">{errorMessage}</p>
|
||||
{:else if helperMessage}
|
||||
<p class="dark:text-white text-sm">
|
||||
<p class="dark:text-white text-sm opacity-70">
|
||||
{helperMessage}
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
@@ -1,30 +1,51 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { Grid, Row, Column } from 'carbon-components-svelte';
|
||||
import isValidDomain from 'is-valid-domain';
|
||||
import { parse } from 'tldts';
|
||||
import { Grid, Row, Column, CopyButton } from 'carbon-components-svelte';
|
||||
import Restart from 'carbon-icons-svelte/lib/Restart.svelte';
|
||||
import TrashCan from 'carbon-icons-svelte/lib/TrashCan.svelte';
|
||||
import ArrowUpRight from 'carbon-icons-svelte/lib/ArrowUpRight.svelte';
|
||||
|
||||
import TextField from '$lib/components/Form/TextField.svelte';
|
||||
import TextChip from '$lib/components/Chip/Text.svelte';
|
||||
import ComingSoon from '$lib/components/ComingSoon/index.svelte';
|
||||
import UploadImage from '$lib/components/UploadImage/index.svelte';
|
||||
import TextArea from '$lib/components/Form/TextArea.svelte';
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
||||
import { currentOrg } from '$lib/utils/store/org';
|
||||
import type { CurrentOrg } from '$lib/utils/types/org';
|
||||
import { supabase } from '$lib/utils/functions/supabase';
|
||||
import { blockedSubdomain } from '$lib/utils/constants/app';
|
||||
import { snackbar } from '$lib/components/Snackbar/store';
|
||||
import SectionTitle from '../SectionTitle.svelte';
|
||||
import VisitOrgSiteButton from '$lib/components/Buttons/VisitOrgSite.svelte';
|
||||
import UpgradeBanner from '$lib/components/Upgrade/Banner.svelte';
|
||||
import IconButton from '$lib/components/IconButton/index.svelte';
|
||||
import { updateOrgSiteNameValidation } from '$lib/utils/functions/validator';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
import { sendDomainRequest } from '$lib/utils/services/org/domain';
|
||||
|
||||
let siteName = '';
|
||||
let customDomain = '';
|
||||
let customCode = '';
|
||||
let favicon = '';
|
||||
let isDomainValid = false;
|
||||
let isLoading = false;
|
||||
let isCustomDomainLoading = false;
|
||||
let isRefreshing = false;
|
||||
|
||||
type Error = {
|
||||
siteName: string;
|
||||
customDomain: string;
|
||||
};
|
||||
|
||||
let siteName = '';
|
||||
let errors: Error = {
|
||||
siteName: ''
|
||||
siteName: '',
|
||||
customDomain: ''
|
||||
};
|
||||
let isLoading = false;
|
||||
|
||||
async function handleChangeDomain() {
|
||||
async function handleSaveSiteName() {
|
||||
errors = updateOrgSiteNameValidation(siteName) as Error;
|
||||
|
||||
if (Object.values(errors).length) {
|
||||
@@ -57,20 +78,129 @@
|
||||
isLoading = false;
|
||||
}
|
||||
|
||||
function setSiteName(_siteName: string) {
|
||||
if (!siteName) {
|
||||
siteName = _siteName;
|
||||
async function handleSaveCustomDomain() {
|
||||
if (!isDomainValid) return;
|
||||
|
||||
const details = parse(customDomain);
|
||||
if (!details.subdomain) {
|
||||
errors.customDomain = $t('components.settings.domains.custom_domain_error');
|
||||
return;
|
||||
}
|
||||
isCustomDomainLoading = true;
|
||||
|
||||
const { error } = await supabase
|
||||
.from('organization')
|
||||
.update({ customDomain })
|
||||
.match({ id: $currentOrg.id });
|
||||
|
||||
if (error) {
|
||||
console.log('Error: create organisation', error);
|
||||
errors.customDomain = error.message;
|
||||
isCustomDomainLoading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await sendDomainRequest('add_domain', customDomain);
|
||||
const data = await response.json();
|
||||
console.log('added domain to vercel', data);
|
||||
} catch (error) {
|
||||
console.log('Error: adding domain to vercel', error);
|
||||
snackbar.error(error as string);
|
||||
isCustomDomainLoading = false;
|
||||
return;
|
||||
}
|
||||
snackbar.success('components.settings.domains.custom_domain_success');
|
||||
$currentOrg.customDomain = customDomain;
|
||||
|
||||
isCustomDomainLoading = false;
|
||||
}
|
||||
|
||||
function resetErrors(_siteName: string) {
|
||||
async function handleRemoveCustomDomain() {
|
||||
if (!$currentOrg.customDomain) return;
|
||||
|
||||
isCustomDomainLoading = true;
|
||||
const { error } = await supabase
|
||||
.from('organization')
|
||||
.update({ customDomain: null, isCustomDomainVerified: false })
|
||||
.match({ id: $currentOrg.id });
|
||||
|
||||
if (error) {
|
||||
console.log('Error: updating organisation', error);
|
||||
snackbar.error(error.message);
|
||||
isCustomDomainLoading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await sendDomainRequest('remove_domain', $currentOrg.customDomain);
|
||||
const data = await response.json();
|
||||
if (!data.success) {
|
||||
throw new Error(data.message);
|
||||
}
|
||||
console.log('removed domain from vercel', data);
|
||||
} catch (error) {
|
||||
console.log('Error: removing domain from vercel', error);
|
||||
snackbar.error(error as string);
|
||||
isCustomDomainLoading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
snackbar.success();
|
||||
|
||||
$currentOrg.customDomain = undefined;
|
||||
$currentOrg.isCustomDomainVerified = false;
|
||||
isCustomDomainLoading = false;
|
||||
}
|
||||
|
||||
async function handleRefreshCustomDomain() {
|
||||
isRefreshing = true;
|
||||
|
||||
try {
|
||||
const response = await sendDomainRequest('verify_domain', $currentOrg.customDomain || '');
|
||||
const data = await response.json();
|
||||
|
||||
console.log('data', data);
|
||||
if (data.verified && !$currentOrg.isCustomDomainVerified) {
|
||||
$currentOrg.isCustomDomainVerified = true;
|
||||
|
||||
await supabase
|
||||
.from('organization')
|
||||
.update({ isCustomDomainVerified: true })
|
||||
.match({ id: $currentOrg.id });
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Error: refreshing domain', error);
|
||||
snackbar.error(error as string);
|
||||
}
|
||||
|
||||
isRefreshing = false;
|
||||
}
|
||||
|
||||
function resetErrors(_siteName: string, _customDomain: string) {
|
||||
if (errors.siteName) {
|
||||
errors.siteName = '';
|
||||
}
|
||||
if (errors.customDomain) {
|
||||
errors.customDomain = '';
|
||||
}
|
||||
}
|
||||
|
||||
$: resetErrors(siteName);
|
||||
$: setSiteName($currentOrg.siteName);
|
||||
function setDefaults(org: CurrentOrg) {
|
||||
if (!siteName) {
|
||||
siteName = org.siteName;
|
||||
}
|
||||
}
|
||||
|
||||
function getSubdomain() {
|
||||
const details = parse($currentOrg.customDomain || '');
|
||||
return details.subdomain;
|
||||
}
|
||||
|
||||
$: setDefaults($currentOrg);
|
||||
$: resetErrors(siteName, customDomain);
|
||||
|
||||
$: isDomainValid = isValidDomain(customDomain, { subdomain: true });
|
||||
</script>
|
||||
|
||||
<Grid class="border rounded border-gray-200 dark:border-neutral-600 w-full mt-5">
|
||||
@@ -80,7 +210,7 @@
|
||||
>
|
||||
<Column sm={2} md={6} lg={8}>
|
||||
<p class="text-md text-gray-500 dark:text-white mb-5">
|
||||
{$t('components.settings.domains.work_together')}
|
||||
{$t('settings.organization.organization_profile.custom_domain.body')}
|
||||
</p>
|
||||
|
||||
<div>
|
||||
@@ -100,7 +230,7 @@
|
||||
label={$t('components.settings.domains.update')}
|
||||
className="py-4"
|
||||
variant={VARIANTS.OUTLINED}
|
||||
onClick={handleChangeDomain}
|
||||
onClick={handleSaveSiteName}
|
||||
isDisabled={isLoading}
|
||||
/>
|
||||
|
||||
@@ -110,15 +240,168 @@
|
||||
</Column>
|
||||
</Row>
|
||||
|
||||
<Row class="py-7 border-bottom-c">
|
||||
<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">{$t('components.settings.domains.domain')}</p>
|
||||
<ComingSoon />
|
||||
<Row class="py-7">
|
||||
<UpgradeBanner className="mb-5">{$t('upgrade.domain')}</UpgradeBanner>
|
||||
<Column sm={2} md={2} lg={4} class="text-lg">
|
||||
<SectionTitle>
|
||||
{$t('components.settings.domains.custom')}
|
||||
</SectionTitle>
|
||||
</Column>
|
||||
<Column sm={2} md={6} lg={10}>
|
||||
<div class="pb-10 border-bottom-c">
|
||||
<!-- DNS Configuration -->
|
||||
{#if $currentOrg.customDomain}
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<p class="font-medium text-md flex items-center gap-2">
|
||||
{$currentOrg.customDomain}
|
||||
|
||||
<IconButton
|
||||
contained={true}
|
||||
size="small"
|
||||
onClick={() => goto(`https://${$currentOrg.customDomain}`)}
|
||||
>
|
||||
<ArrowUpRight size={16} />
|
||||
</IconButton>
|
||||
</p>
|
||||
|
||||
<div
|
||||
class="mt-1 h-2 w-2 rounded-full bg-{$currentOrg.isCustomDomainVerified
|
||||
? 'green'
|
||||
: 'yellow'}-400"
|
||||
></div>
|
||||
</div>
|
||||
{#if $currentOrg.isCustomDomainVerified}
|
||||
<TextChip
|
||||
value="Verified"
|
||||
className="bg-green-500 text-white text-xs px-3"
|
||||
size="sm"
|
||||
/>
|
||||
{:else}
|
||||
<TextChip
|
||||
value="Pending verification"
|
||||
className="bg-yellow-500 text-white text-xs px-3"
|
||||
size="sm"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="text-sm text-gray-500 mb-4">
|
||||
{$t('components.settings.domains.dns_description')}
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-10 mb-4 border rounded-md py-2 px-4">
|
||||
<div class="h-[72px] justify-evenly flex flex-col">
|
||||
<p class="font-light text-sm">{$t('components.settings.domains.dns_type')}</p>
|
||||
<p class="h-[40px] flex items-center">CNAME</p>
|
||||
</div>
|
||||
|
||||
<div class="h-[72px] justify-evenly flex flex-col">
|
||||
<p class="font-light text-sm">{$t('components.settings.domains.dns_name')}</p>
|
||||
<p class="h-[40px] flex items-center">
|
||||
{getSubdomain()}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="h-[72px] justify-evenly flex flex-col">
|
||||
<p class="font-light text-sm">{$t('components.settings.domains.dns_value')}</p>
|
||||
<p class=" flex items-center gap-1">
|
||||
cname.vercel-dns.com
|
||||
<CopyButton text="cname.vercel-dns.com" />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between mt-5">
|
||||
<PrimaryButton
|
||||
className="py-4 flex items-center gap-2"
|
||||
onClick={handleRefreshCustomDomain}
|
||||
isLoading={isRefreshing}
|
||||
variant={VARIANTS.OUTLINED}
|
||||
>
|
||||
{#if !isRefreshing}
|
||||
<Restart size={16} />
|
||||
{/if}
|
||||
{$t('components.settings.domains.refresh')}
|
||||
</PrimaryButton>
|
||||
|
||||
<PrimaryButton
|
||||
className="py-4 flex items-center gap-2"
|
||||
onClick={handleRemoveCustomDomain}
|
||||
isLoading={isCustomDomainLoading}
|
||||
variant={VARIANTS.CONTAINED_DANGER}
|
||||
>
|
||||
{#if !isCustomDomainLoading}
|
||||
<TrashCan size={16} />
|
||||
{/if}
|
||||
{$t('components.settings.domains.remove')}
|
||||
</PrimaryButton>
|
||||
</div>
|
||||
{:else}
|
||||
<!-- Add Custom Domain -->
|
||||
<div class="flex items-center gap-5 mb-4">
|
||||
<p class="font-bold">{$t('components.settings.domains.your_domain')}</p>
|
||||
</div>
|
||||
<TextField
|
||||
bind:value={customDomain}
|
||||
type="text"
|
||||
placeholder="courses.yourwebsite.com"
|
||||
helperMessage="https://{customDomain || 'course.yourwebsite.com'}"
|
||||
errorMessage={errors.customDomain}
|
||||
className="w-4/5"
|
||||
/>
|
||||
|
||||
<div class="flex items-center mt-5">
|
||||
<PrimaryButton
|
||||
label={$t('components.settings.domains.save')}
|
||||
className="py-4"
|
||||
onClick={handleSaveCustomDomain}
|
||||
isLoading={isCustomDomainLoading}
|
||||
isDisabled={isLoading || !isDomainValid}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="py-10 border-bottom-c">
|
||||
<div class="flex items-center gap-5 mb-4">
|
||||
<p class="font-bold">{$t('components.settings.domains.custom_favicon')}</p>
|
||||
<ComingSoon />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<UploadImage
|
||||
shape="rounded-md"
|
||||
bind:avatar={favicon}
|
||||
src={$currentOrg.favicon ?? '/logo-512.png'}
|
||||
widthHeight="w-16 h-16 lg:w-24 lg:h-24"
|
||||
flexDirection="flex-row"
|
||||
isDisabled={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="py-10">
|
||||
<div class="flex items-center gap-5 mb-4">
|
||||
<p class="font-bold">{$t('components.settings.domains.custom_code')}</p>
|
||||
<ComingSoon />
|
||||
</div>
|
||||
|
||||
<TextArea
|
||||
bind:value={customCode}
|
||||
placeholder="e.g <link rel='stylesheet' href='https://example.com/style.css' />"
|
||||
className="w-4/5"
|
||||
rows={7}
|
||||
disabled={true}
|
||||
/>
|
||||
</div>
|
||||
<!-- <div class="flex items-center">
|
||||
<p class="dark:text-white mr-3">
|
||||
{$t('components.settings.domains.domain')}
|
||||
</p>
|
||||
|
||||
<ComingSoon />
|
||||
</div> -->
|
||||
</Column>
|
||||
</Row>
|
||||
</Grid>
|
||||
|
||||
@@ -234,11 +234,15 @@
|
||||
{$t('settings.organization.organization_profile.custom_domain.body')}
|
||||
</p>
|
||||
<PrimaryButton
|
||||
label={$t('settings.organization.organization_profile.custom_domain.button')}
|
||||
className="my-7 py-5 px-10"
|
||||
className="my-7 py-5 px-10 flex items-center gap-2 justify-center"
|
||||
variant={VARIANTS.OUTLINED}
|
||||
onClick={() => gotoSetting('/domains')}
|
||||
/>
|
||||
>
|
||||
{#if $isFreePlan}
|
||||
<FlashFilled size={16} class="text-blue-700" />
|
||||
{/if}
|
||||
{$t('settings.organization.organization_profile.custom_domain.button')}
|
||||
</PrimaryButton>
|
||||
</Column>
|
||||
</Row>
|
||||
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
|
||||
|
||||
@@ -157,21 +157,23 @@
|
||||
style="background-color: {isYearlyPlan ? 'initial' : '#1D4EE2'}; color: {isYearlyPlan
|
||||
? '#5e636b'
|
||||
: '#fff'}"
|
||||
class="rounded-[30px] bg-blue-700 px-3 py-1 text-xs text-white lg:px-4 lg:py-2"
|
||||
on:click={toggleIsYearlyPlan}>{$t('pricing.modal.monthly')}</button
|
||||
class="rounded-[30px] bg-blue-700 px-3 py-1 text-xs text-white lg:px-4 lg:py-2 transition-all duration-500 ease-in-out"
|
||||
on:click={toggleIsYearlyPlan}
|
||||
>
|
||||
{$t('pricing.modal.monthly')}
|
||||
</button>
|
||||
<button
|
||||
style="background-color: {isYearlyPlan ? '#1D4EE2' : ''}; color: {isYearlyPlan
|
||||
? '#fff'
|
||||
: '#5e636b'}"
|
||||
class="relative rounded-[30px] px-3 py-1 text-xs text-white lg:px-4 lg:py-2"
|
||||
class="relative rounded-[30px] px-3 py-1 text-xs text-white lg:px-4 lg:py-2 transition-all duration-500 ease-in-out"
|
||||
on:click={toggleIsYearlyPlan}
|
||||
>
|
||||
{$t('pricing.modal.annually')}
|
||||
<div
|
||||
class="absolute right-[-40%] -top-4 scale-[90%] rounded-full bg-[#006600] px-1.5 py-1 text-[0.7rem] text-white"
|
||||
>
|
||||
Save 2 months
|
||||
{$t('pricing.modal.save')}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -7,22 +7,33 @@
|
||||
export let widthHeight = '';
|
||||
export let shape = 'rounded-full';
|
||||
export let errorMessage: string | null = null;
|
||||
export let isDisabled = false;
|
||||
export let maxFileSizeInMb: number = 2; // Default max file size 2MB
|
||||
export let flexDirection = 'flex-col';
|
||||
|
||||
let fileinput: HTMLInputElement;
|
||||
|
||||
const onFileSelected = (e: Event<HTMLInputElement>) => {
|
||||
const image = e.target.files[0];
|
||||
const maxFileSize = maxFileSizeInMb * 1024 * 1024;
|
||||
if (image.size > maxFileSize) {
|
||||
errorMessage = `${$t('settings.profile.profile_picture.validation_error')} ${
|
||||
maxFileSize / (1024 * 1024)
|
||||
} MB`;
|
||||
return;
|
||||
}
|
||||
let reader = new FileReader();
|
||||
reader.readAsDataURL(image);
|
||||
reader.onload = (e) => {
|
||||
avatar = image;
|
||||
// @ts-ignore
|
||||
src = e.target?.result || undefined;
|
||||
errorMessage = null; // Clear error message on successful load
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<section class="width-fit p-3 flex flex-col items-center">
|
||||
<section class="width-fit p-3 flex {flexDirection} items-center justify-between gap-5">
|
||||
<div
|
||||
class="avatar-container {widthHeight ||
|
||||
'setwidthheight'} pointer border-2 border-gray-200 dark:border-neutral-600 relative {shape}"
|
||||
@@ -40,18 +51,30 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="width-fit text-primary-700 flex items-center cursor-pointer text-sm mt-3"
|
||||
on:click={() => {
|
||||
fileinput.click();
|
||||
}}
|
||||
>
|
||||
<Camera size={20} />
|
||||
<span class="ml-2">{$t('settings.profile.profile_picture.upload_image')}</span>
|
||||
</button>
|
||||
{#if errorMessage}
|
||||
<p class="text-sm text-red-500">{errorMessage}</p>
|
||||
{/if}
|
||||
<div class="flex flex-col items-center">
|
||||
<button
|
||||
class="width-fit text-primary-700 flex flex-col items-center text-sm {isDisabled
|
||||
? 'opacity-50 cursor-not-allowed'
|
||||
: 'cursor-pointer'}"
|
||||
on:click={() => {
|
||||
if (!isDisabled) {
|
||||
fileinput.click();
|
||||
}
|
||||
}}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
<Camera size={20} />
|
||||
<span class="ml-2">{$t('settings.profile.profile_picture.upload_image')}</span>
|
||||
</button>
|
||||
<p class="text-xs text-center text-gray-500">
|
||||
{$t('settings.profile.profile_picture.file_size')}
|
||||
{maxFileSizeInMb}MB<br />
|
||||
{$t('settings.profile.profile_picture.accepted')}: jpeg, jpg, png, webp
|
||||
</p>
|
||||
{#if errorMessage}
|
||||
<p class="text-sm text-red-500">{errorMessage}</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<input
|
||||
style="display:none"
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
// Code from https://github.com/vercel/platforms/blob/1ed55b13bcae97b037c69ec40b4c32df21c2412c/lib/domains.ts
|
||||
|
||||
import {
|
||||
DomainResponse,
|
||||
DomainConfigResponse,
|
||||
DomainVerificationResponse
|
||||
} from '$lib/utils/types/org';
|
||||
import { PROJECT_ID_VERCEL, TEAM_ID_VERCEL, AUTH_BEARER_TOKEN } from '$env/static/private';
|
||||
|
||||
export const addDomainToVercel = async (domain: string) => {
|
||||
return await fetch(
|
||||
`https://api.vercel.com/v10/projects/${PROJECT_ID_VERCEL}/domains${
|
||||
TEAM_ID_VERCEL ? `?teamId=${TEAM_ID_VERCEL}` : ''
|
||||
}`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${AUTH_BEARER_TOKEN}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: domain
|
||||
// Optional: Redirect www. to root domain
|
||||
// ...(domain.startsWith("www.") && {
|
||||
// redirect: domain.replace("www.", ""),
|
||||
// }),
|
||||
})
|
||||
}
|
||||
).then((res) => res.json());
|
||||
};
|
||||
|
||||
export const removeDomainFromVercelProject = async (domain: string) => {
|
||||
return await fetch(
|
||||
`https://api.vercel.com/v9/projects/${PROJECT_ID_VERCEL}/domains/${domain}${
|
||||
TEAM_ID_VERCEL ? `?teamId=${TEAM_ID_VERCEL}` : ''
|
||||
}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${AUTH_BEARER_TOKEN}`
|
||||
},
|
||||
method: 'DELETE'
|
||||
}
|
||||
).then((res) => res.json());
|
||||
};
|
||||
|
||||
export const removeDomainFromVercelTeam = async (domain: string) => {
|
||||
return await fetch(
|
||||
`https://api.vercel.com/v6/domains/${domain}${
|
||||
TEAM_ID_VERCEL ? `?teamId=${TEAM_ID_VERCEL}` : ''
|
||||
}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${AUTH_BEARER_TOKEN}`
|
||||
},
|
||||
method: 'DELETE'
|
||||
}
|
||||
).then((res) => res.json());
|
||||
};
|
||||
|
||||
export const getDomainResponse = async (
|
||||
domain: string
|
||||
): Promise<DomainResponse & { error: { code: string; message: string } }> => {
|
||||
return await fetch(
|
||||
`https://api.vercel.com/v9/projects/${PROJECT_ID_VERCEL}/domains/${domain}${
|
||||
TEAM_ID_VERCEL ? `?teamId=${TEAM_ID_VERCEL}` : ''
|
||||
}`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `Bearer ${AUTH_BEARER_TOKEN}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
).then((res) => {
|
||||
return res.json();
|
||||
});
|
||||
};
|
||||
|
||||
export const getConfigResponse = async (domain: string): Promise<DomainConfigResponse> => {
|
||||
return await fetch(
|
||||
`https://api.vercel.com/v6/domains/${domain}/config${
|
||||
TEAM_ID_VERCEL ? `?teamId=${TEAM_ID_VERCEL}` : ''
|
||||
}`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `Bearer ${AUTH_BEARER_TOKEN}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
).then((res) => res.json());
|
||||
};
|
||||
|
||||
export const verifyDomain = async (domain: string): Promise<DomainVerificationResponse> => {
|
||||
return await fetch(
|
||||
`https://api.vercel.com/v9/projects/${PROJECT_ID_VERCEL}/domains/${domain}/verify${
|
||||
TEAM_ID_VERCEL ? `?teamId=${TEAM_ID_VERCEL}` : ''
|
||||
}`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${AUTH_BEARER_TOKEN}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
).then((res) => res.json());
|
||||
};
|
||||
|
||||
export const getSubdomain = (name: string, apexName: string) => {
|
||||
if (name === apexName) return null;
|
||||
return name.slice(0, name.length - apexName.length - 1);
|
||||
};
|
||||
|
||||
export const getApexDomain = (url: string) => {
|
||||
let domain;
|
||||
try {
|
||||
domain = new URL(url).hostname;
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
const parts = domain.split('.');
|
||||
if (parts.length > 2) {
|
||||
// if it's a subdomain (e.g. project.vercel.app), return the last 2 parts
|
||||
return parts.slice(-2).join('.');
|
||||
}
|
||||
// if it's a normal domain (e.g. training.yourdomain.com), we return the domain
|
||||
return domain;
|
||||
};
|
||||
|
||||
// courtesy of ChatGPT: https://sharegpt.com/c/pUYXtRs
|
||||
export const validDomainRegex = new RegExp(
|
||||
/^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/
|
||||
);
|
||||
@@ -0,0 +1,15 @@
|
||||
import { supabase } from '$lib/utils/functions/supabase';
|
||||
|
||||
export async function sendDomainRequest(key: string, domain: string): Promise<Response> {
|
||||
const { data } = await supabase.auth.getSession();
|
||||
const accessToken = data.session?.access_token || '';
|
||||
|
||||
return fetch('/api/domain', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ params: { key, domain } }),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `${accessToken}`
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -73,15 +73,7 @@ export async function getOrganizations(userId: string, isOrgSite?: boolean, orgS
|
||||
role_id,
|
||||
created_at,
|
||||
organization!organizationmember_organization_id_fkey (
|
||||
id,
|
||||
name,
|
||||
siteName,
|
||||
avatar_url,
|
||||
landingpage,
|
||||
customization,
|
||||
is_restricted,
|
||||
theme,
|
||||
created_at,
|
||||
*,
|
||||
organization_plan(
|
||||
plan_name,
|
||||
is_active,
|
||||
@@ -107,18 +99,10 @@ export async function getOrganizations(userId: string, isOrgSite?: boolean, orgS
|
||||
if (Array.isArray(data) && data.length) {
|
||||
data.forEach((orgMember) => {
|
||||
orgsArray.push({
|
||||
id: orgMember?.organization?.id,
|
||||
name: orgMember?.organization?.name,
|
||||
shortName: orgMember?.organization?.name?.substring(0, 2)?.toUpperCase() || '',
|
||||
siteName: orgMember?.organization?.siteName,
|
||||
theme: orgMember?.organization?.theme,
|
||||
avatar_url: orgMember?.organization?.avatar_url,
|
||||
...(orgMember?.organization || {}),
|
||||
memberId: orgMember?.id,
|
||||
role_id: orgMember?.role_id,
|
||||
landingpage: orgMember?.organization?.landingpage,
|
||||
is_restricted: orgMember?.organization?.is_restricted,
|
||||
customization: orgMember?.organization?.customization,
|
||||
organization_plan: orgMember?.organization?.organization_plan
|
||||
role_id: parseInt(orgMember?.role_id),
|
||||
shortName: orgMember?.organization?.name?.substring(0, 2)?.toUpperCase() || ''
|
||||
});
|
||||
});
|
||||
|
||||
@@ -220,7 +204,7 @@ export async function getCourseBySiteName(siteName: string) {
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function getCurrentOrg(siteName: string, justGet = false) {
|
||||
export async function getCurrentOrg(siteName: string, justGet = false, isCustomDomain = false) {
|
||||
const { data, error } = await supabase
|
||||
.from('organization')
|
||||
.select(
|
||||
@@ -233,13 +217,18 @@ export async function getCurrentOrg(siteName: string, justGet = false) {
|
||||
is_restricted,
|
||||
customization,
|
||||
theme,
|
||||
favicon,
|
||||
customDomain,
|
||||
isCustomDomainVerified,
|
||||
customCode,
|
||||
organization_plan(
|
||||
plan_name,
|
||||
is_active
|
||||
)
|
||||
`
|
||||
)
|
||||
.eq('siteName', siteName)
|
||||
.eq(isCustomDomain ? 'customDomain' : 'siteName', siteName)
|
||||
.filter('isCustomDomainVerified', 'eq', isCustomDomain ? true : undefined)
|
||||
.returns<CurrentOrg[]>();
|
||||
console.log('data =', data);
|
||||
console.log('error =', error);
|
||||
|
||||
@@ -194,7 +194,8 @@
|
||||
"team_members": "Teammitglieder",
|
||||
"back": "Zurück zu den Einstellungen",
|
||||
"download_lessons": "Führen Sie ein Upgrade durch, um Lektionen herunterzuladen",
|
||||
"download_course": "Upgrade zum Download-Kurs"
|
||||
"download_course": "Upgrade zum Download-Kurs",
|
||||
"domain": "Aktualisieren Sie Ihren Plan, um benutzerdefinierte Domänen hinzuzufügen"
|
||||
},
|
||||
"audience": {
|
||||
"title": "Publikum",
|
||||
@@ -923,12 +924,23 @@
|
||||
"settings": {
|
||||
"domains": {
|
||||
"add": "Hinzufügen",
|
||||
"work_together": "Fügen Sie Ihre Teamkollegen oder Mitarbeiter zu Ihrer Organisation hinzu. Beginnen Sie mit der Zusammenarbeit",
|
||||
"url": "URL",
|
||||
"update": "Domäne aktualisieren",
|
||||
"custom": "Benutzerdefinierten Domain",
|
||||
"domain": "Fügen Sie Ihren eigenen Domainnamen hinzu",
|
||||
"title": "Benutzerdefinierten Domain"
|
||||
"title": "Benutzerdefinierten Domain",
|
||||
"save": "Speichern",
|
||||
"your_domain": "Ihre Domain",
|
||||
"custom_domain_error": "Sie können eine Top-Level-Domain nicht als benutzerdefinierte Domain hinzufügen. Verwenden Sie stattdessen eine Subdomain wie „Kurse.[IhreWebsite].com“ oder „www.[IhreWebsite].com“.",
|
||||
"custom_domain_success": "Benutzerdefinierte Domäne erfolgreich hinzugefügt",
|
||||
"dns_description": "Legen Sie bei Ihrem DNS-Anbieter den folgenden Eintrag fest, um fortzufahren:",
|
||||
"dns_type": "Typ",
|
||||
"dns_value": "Wert",
|
||||
"dns_name": "Name",
|
||||
"refresh": "Aktualisieren",
|
||||
"remove": "Entfernen",
|
||||
"custom_favicon": "Benutzerdefiniertes Favicon",
|
||||
"custom_code": "Benutzerdefinierter Code"
|
||||
},
|
||||
"customize-lms": {
|
||||
"title": "LMS anpassen",
|
||||
@@ -1084,7 +1096,8 @@
|
||||
"learn": "Erfahren Sie mehr",
|
||||
"month": "Monat",
|
||||
"monthly": "Monatlich",
|
||||
"annually": "Jährlich"
|
||||
"annually": "Jährlich",
|
||||
"save": "Sparen Sie 2 Monate"
|
||||
},
|
||||
"plan_names": {
|
||||
"free": "Frei",
|
||||
@@ -1206,9 +1219,9 @@
|
||||
"update_organization": "Organisation aktualisieren",
|
||||
"team": {
|
||||
"heading": "Team",
|
||||
"sub_heading": "Richten Sie Ihre Website ein",
|
||||
"body": "Fügen Sie Ihrer Organisation Teamkollegen hinzu, damit Sie beide an Projekten zusammenarbeiten können.",
|
||||
"button": "Team verwalten"
|
||||
"button": "Team verwalten",
|
||||
"sub_heading": "Stellen Sie Ihr Team zusammen"
|
||||
},
|
||||
"custom_domain": {
|
||||
"heading": "Benutzerdefinierten Domain",
|
||||
@@ -1239,7 +1252,10 @@
|
||||
},
|
||||
"profile_picture": {
|
||||
"heading": "Profilbild",
|
||||
"upload_image": "Bild hochladen"
|
||||
"upload_image": "Bild hochladen",
|
||||
"file_size": "Maximale Dateigröße:",
|
||||
"accepted": "Akzeptiert:",
|
||||
"validation_error": "Die Dateigröße überschreitet die maximale Grenze:"
|
||||
}
|
||||
},
|
||||
"tabs": {
|
||||
|
||||
@@ -66,7 +66,10 @@
|
||||
"profile": {
|
||||
"profile_picture": {
|
||||
"heading": "Profile Picture",
|
||||
"upload_image": "Upload Image"
|
||||
"upload_image": "Upload Image",
|
||||
"file_size": "Max file size:",
|
||||
"accepted": "Accepted:",
|
||||
"validation_error": "File size exceeds the maximum limit:"
|
||||
},
|
||||
"personal_information": {
|
||||
"heading": "Personal Information",
|
||||
@@ -101,7 +104,7 @@
|
||||
},
|
||||
"team": {
|
||||
"heading": "Team",
|
||||
"sub_heading": "Set up your website",
|
||||
"sub_heading": "Set up your team",
|
||||
"body": "Add team mates to your organization so you can both collaborate on projects.",
|
||||
"button": "Manage Team"
|
||||
}
|
||||
@@ -224,6 +227,7 @@
|
||||
"heading": "Upgrade Plan",
|
||||
"thanks": "Thank you for your support",
|
||||
"plan": "Your plan comes with cool perks and benefits including access to all future features we release.",
|
||||
"save": "Save 2 months",
|
||||
"close": "Close",
|
||||
"learn": "Learn more",
|
||||
"month": "month",
|
||||
@@ -332,11 +336,22 @@
|
||||
"domains": {
|
||||
"title": "Custom domain",
|
||||
"add": "Add",
|
||||
"work_together": "Add your team mates or collaborators to your organization. Start working together",
|
||||
"url": "URL",
|
||||
"update": "Update Domain",
|
||||
"custom": "Custom domain",
|
||||
"domain": "Add your own domain name"
|
||||
"domain": "Add your own domain name",
|
||||
"save": "Save",
|
||||
"your_domain": "Your Domain",
|
||||
"custom_domain_error": "You can't add a top level domain as custom domain. Instead, use a subdomain like 'courses.[yourwebsite].com' or 'www.[yourwebsite].com'.",
|
||||
"custom_domain_success": "Custom domain added successfully",
|
||||
"dns_description": "Set the following record on your DNS provider to continue:",
|
||||
"dns_type": "Type",
|
||||
"dns_value": "Value",
|
||||
"dns_name": "Name",
|
||||
"refresh": "Refresh",
|
||||
"remove": "Remove",
|
||||
"custom_favicon": "Custom favicon",
|
||||
"custom_code": "Custom code"
|
||||
},
|
||||
"customize_lms": {
|
||||
"title": "Customize LMS",
|
||||
@@ -1119,6 +1134,7 @@
|
||||
"upgrade": {
|
||||
"certificate": "Upgrade your plan to generate certificates",
|
||||
"team": "Upgrade your plan to add team members",
|
||||
"domain": "Upgrade your plan to add custom domains",
|
||||
"team_members": "Team members",
|
||||
"back": "Back to Settings",
|
||||
"download_lessons": "Upgrade to download lessons",
|
||||
|
||||
@@ -194,7 +194,8 @@
|
||||
"team_members": "Miembros del equipo",
|
||||
"back": "Volver a configuración",
|
||||
"download_lessons": "Actualice para descargar lecciones",
|
||||
"download_course": "Actualizar para descargar el curso"
|
||||
"download_course": "Actualizar para descargar el curso",
|
||||
"domain": "Actualice su plan para agregar dominios personalizados"
|
||||
},
|
||||
"audience": {
|
||||
"title": "Audiencia",
|
||||
@@ -923,12 +924,23 @@
|
||||
"settings": {
|
||||
"domains": {
|
||||
"add": "Agregar",
|
||||
"work_together": "Agregue sus compañeros de equipo o colaboradores a su organización. Empezar a trabajar juntos",
|
||||
"url": "URL",
|
||||
"update": "Actualizar dominio",
|
||||
"custom": "Dominio personalizado",
|
||||
"domain": "Añade tu propio nombre de dominio",
|
||||
"title": "Dominio personalizado"
|
||||
"title": "Dominio personalizado",
|
||||
"save": "Ahorrar",
|
||||
"your_domain": "Tu dominio",
|
||||
"custom_domain_error": "No puede agregar un dominio de nivel superior como dominio personalizado. En su lugar, utilice un subdominio como 'cursos.[susitio web].com' o 'www.[susitio web].com'.",
|
||||
"custom_domain_success": "Dominio personalizado agregado exitosamente",
|
||||
"dns_description": "Establezca el siguiente registro en su proveedor de DNS para continuar:",
|
||||
"dns_type": "Tipo",
|
||||
"dns_value": "Valor",
|
||||
"dns_name": "Nombre",
|
||||
"refresh": "Refrescar",
|
||||
"remove": "Eliminar",
|
||||
"custom_favicon": "favicon personalizado",
|
||||
"custom_code": "código personalizado"
|
||||
},
|
||||
"customize-lms": {
|
||||
"title": "Personalizar LMS",
|
||||
@@ -1084,7 +1096,8 @@
|
||||
"learn": "Aprende más",
|
||||
"month": "mes",
|
||||
"monthly": "Mensual",
|
||||
"annually": "Anualmente"
|
||||
"annually": "Anualmente",
|
||||
"save": "Ahorra 2 meses"
|
||||
},
|
||||
"plan_names": {
|
||||
"free": "Gratis",
|
||||
@@ -1206,9 +1219,9 @@
|
||||
"update_organization": "Actualizar organización",
|
||||
"team": {
|
||||
"heading": "Equipo",
|
||||
"sub_heading": "Configura tu sitio web",
|
||||
"body": "Agregue compañeros de equipo a su organización para que ambos puedan colaborar en proyectos.",
|
||||
"button": "Gestionar equipo"
|
||||
"button": "Gestionar equipo",
|
||||
"sub_heading": "Configura tu equipo"
|
||||
},
|
||||
"custom_domain": {
|
||||
"heading": "Dominio personalizado",
|
||||
@@ -1239,7 +1252,10 @@
|
||||
},
|
||||
"profile_picture": {
|
||||
"heading": "Foto de perfil",
|
||||
"upload_image": "Cargar imagen"
|
||||
"upload_image": "Cargar imagen",
|
||||
"file_size": "Tamaño máximo de archivo:",
|
||||
"accepted": "Aceptado:",
|
||||
"validation_error": "El tamaño del archivo excede el límite máximo:"
|
||||
}
|
||||
},
|
||||
"tabs": {
|
||||
|
||||
@@ -194,7 +194,8 @@
|
||||
"team_members": "Membres de l'équipe",
|
||||
"back": "Retour aux paramètres",
|
||||
"download_lessons": "Mettre à niveau pour télécharger les leçons",
|
||||
"download_course": "Mettre à niveau pour télécharger le cours"
|
||||
"download_course": "Mettre à niveau pour télécharger le cours",
|
||||
"domain": "Mettez à niveau votre forfait pour ajouter des domaines personnalisés"
|
||||
},
|
||||
"audience": {
|
||||
"title": "Public",
|
||||
@@ -923,12 +924,23 @@
|
||||
"settings": {
|
||||
"domains": {
|
||||
"add": "Ajouter",
|
||||
"work_together": "Ajoutez vos coéquipiers ou collaborateurs à votre organisation. Commencez à travailler ensemble",
|
||||
"url": "URL",
|
||||
"update": "Mettre à jour le domaine",
|
||||
"custom": "Domaine personnalisé",
|
||||
"domain": "Ajoutez votre propre nom de domaine",
|
||||
"title": "Domaine personnalisé"
|
||||
"title": "Domaine personnalisé",
|
||||
"save": "Sauvegarder",
|
||||
"your_domain": "Votre domaine",
|
||||
"custom_domain_error": "Vous ne pouvez pas ajouter un domaine de premier niveau en tant que domaine personnalisé. Utilisez plutôt un sous-domaine tel que « courses.[votresite Web].com » ou « www.[votre site Web].com ».",
|
||||
"custom_domain_success": "Domaine personnalisé ajouté avec succès",
|
||||
"dns_description": "Définissez l'enregistrement suivant sur votre fournisseur DNS pour continuer :",
|
||||
"dns_type": "Taper",
|
||||
"dns_value": "Valeur",
|
||||
"dns_name": "Nom",
|
||||
"refresh": "Rafraîchir",
|
||||
"remove": "Retirer",
|
||||
"custom_favicon": "Icône de favori personnalisée",
|
||||
"custom_code": "Code personnalisé"
|
||||
},
|
||||
"customize-lms": {
|
||||
"title": "Personnaliser le LMS",
|
||||
@@ -1084,7 +1096,8 @@
|
||||
"learn": "Apprendre encore plus",
|
||||
"month": "mois",
|
||||
"monthly": "Mensuel",
|
||||
"annually": "Annuellement"
|
||||
"annually": "Annuellement",
|
||||
"save": "Économisez 2 mois"
|
||||
},
|
||||
"plan_names": {
|
||||
"free": "Gratuit",
|
||||
@@ -1206,9 +1219,9 @@
|
||||
"update_organization": "Mettre à jour l'organisation",
|
||||
"team": {
|
||||
"heading": "Équipe",
|
||||
"sub_heading": "Configurez votre site Web",
|
||||
"body": "Ajoutez des coéquipiers à votre organisation afin que vous puissiez tous les deux collaborer sur des projets.",
|
||||
"button": "Gérer l'équipe"
|
||||
"button": "Gérer l'équipe",
|
||||
"sub_heading": "Configurez votre équipe"
|
||||
},
|
||||
"custom_domain": {
|
||||
"heading": "Domaine personnalisé",
|
||||
@@ -1239,7 +1252,10 @@
|
||||
},
|
||||
"profile_picture": {
|
||||
"heading": "Image de profil",
|
||||
"upload_image": "Télécharger une image"
|
||||
"upload_image": "Télécharger une image",
|
||||
"file_size": "Taille maximale du fichier :",
|
||||
"accepted": "Accepté:",
|
||||
"validation_error": "La taille du fichier dépasse la limite maximale :"
|
||||
}
|
||||
},
|
||||
"tabs": {
|
||||
|
||||
@@ -194,7 +194,8 @@
|
||||
"team_members": "टीम के सदस्य",
|
||||
"back": "सेटिंग्स पर वापस जाएँ",
|
||||
"download_lessons": "पाठ डाउनलोड करने के लिए अपग्रेड करें",
|
||||
"download_course": "पाठ्यक्रम डाउनलोड करने के लिए अपग्रेड करें"
|
||||
"download_course": "पाठ्यक्रम डाउनलोड करने के लिए अपग्रेड करें",
|
||||
"domain": "कस्टम डोमेन जोड़ने के लिए अपनी योजना को अपग्रेड करें"
|
||||
},
|
||||
"audience": {
|
||||
"title": "श्रोता",
|
||||
@@ -923,12 +924,23 @@
|
||||
"settings": {
|
||||
"domains": {
|
||||
"add": "जोड़ना",
|
||||
"work_together": "अपने टीम के साथियों या सहयोगियों को अपने संगठन में जोड़ें। एक साथ काम करना शुरू करें",
|
||||
"url": "यूआरएल",
|
||||
"update": "डोमेन अद्यतन करें",
|
||||
"custom": "कस्टम डोमेन",
|
||||
"domain": "अपना स्वयं का डोमेन नाम जोड़ें",
|
||||
"title": "कस्टम डोमेन"
|
||||
"title": "कस्टम डोमेन",
|
||||
"save": "बचाना",
|
||||
"your_domain": "आपका डोमेन",
|
||||
"custom_domain_error": "आप किसी शीर्ष स्तरीय डोमेन को कस्टम डोमेन के रूप में नहीं जोड़ सकते. इसके बजाय, 'courses.[yourwebsite].com' या 'www.[yourwebsite].com' जैसे उपडोमेन का उपयोग करें।",
|
||||
"custom_domain_success": "कस्टम डोमेन सफलतापूर्वक जोड़ा गया",
|
||||
"dns_description": "जारी रखने के लिए अपने DNS प्रदाता पर निम्नलिखित रिकॉर्ड सेट करें:",
|
||||
"dns_type": "प्रकार",
|
||||
"dns_value": "कीमत",
|
||||
"dns_name": "नाम",
|
||||
"refresh": "ताज़ा करना",
|
||||
"remove": "निकालना",
|
||||
"custom_favicon": "कस्टम फ़ेविकॉन",
|
||||
"custom_code": "कस्टम कोड"
|
||||
},
|
||||
"customize-lms": {
|
||||
"title": "एलएमएस को अनुकूलित करें",
|
||||
@@ -1084,7 +1096,8 @@
|
||||
"learn": "और अधिक जानें",
|
||||
"month": "महीना",
|
||||
"monthly": "महीने के",
|
||||
"annually": "हर साल"
|
||||
"annually": "हर साल",
|
||||
"save": "2 महीने बचाएं"
|
||||
},
|
||||
"plan_names": {
|
||||
"free": "मुक्त",
|
||||
@@ -1206,9 +1219,9 @@
|
||||
"update_organization": "संगठन अद्यतन करें",
|
||||
"team": {
|
||||
"heading": "टीम",
|
||||
"sub_heading": "अपनी वेबसाइट सेट करें",
|
||||
"body": "अपने संगठन में टीम के साथियों को जोड़ें ताकि आप दोनों परियोजनाओं पर सहयोग कर सकें।",
|
||||
"button": "टीम का प्रबंधन करें"
|
||||
"button": "टीम का प्रबंधन करें",
|
||||
"sub_heading": "अपनी टीम स्थापित करें"
|
||||
},
|
||||
"custom_domain": {
|
||||
"heading": "कस्टम डोमेन",
|
||||
@@ -1239,7 +1252,10 @@
|
||||
},
|
||||
"profile_picture": {
|
||||
"heading": "प्रोफ़ाइल फोटो",
|
||||
"upload_image": "तस्विर अपलोड करना"
|
||||
"upload_image": "तस्विर अपलोड करना",
|
||||
"file_size": "अधिकतम फ़ाइल आकार:",
|
||||
"accepted": "स्वीकृत:",
|
||||
"validation_error": "फ़ाइल का आकार अधिकतम सीमा से अधिक है:"
|
||||
}
|
||||
},
|
||||
"tabs": {
|
||||
|
||||
@@ -194,7 +194,8 @@
|
||||
"team_members": "Membros do time",
|
||||
"back": "Voltar para configurações",
|
||||
"download_lessons": "Atualize para baixar lições",
|
||||
"download_course": "Atualize para baixar o curso"
|
||||
"download_course": "Atualize para baixar o curso",
|
||||
"domain": "Atualize seu plano para adicionar domínios personalizados"
|
||||
},
|
||||
"audience": {
|
||||
"title": "Público",
|
||||
@@ -923,12 +924,23 @@
|
||||
"settings": {
|
||||
"domains": {
|
||||
"add": "Adicionar",
|
||||
"work_together": "Adicione seus companheiros de equipe ou colaboradores à sua organização. Comece a trabalhar juntos",
|
||||
"url": "URL",
|
||||
"update": "Atualizar domínio",
|
||||
"custom": "Domínio personalizado",
|
||||
"domain": "Adicione seu próprio nome de domínio",
|
||||
"title": "Domínio personalizado"
|
||||
"title": "Domínio personalizado",
|
||||
"save": "Salvar",
|
||||
"your_domain": "Seu domínio",
|
||||
"custom_domain_error": "Você não pode adicionar um domínio de nível superior como domínio personalizado. Em vez disso, use um subdomínio como 'cursos.[seusite].com' ou 'www.[seusite].com'.",
|
||||
"custom_domain_success": "Domínio personalizado adicionado com sucesso",
|
||||
"dns_description": "Defina o seguinte registro em seu provedor DNS para continuar:",
|
||||
"dns_type": "Tipo",
|
||||
"dns_value": "Valor",
|
||||
"dns_name": "Nome",
|
||||
"refresh": "Atualizar",
|
||||
"remove": "Remover",
|
||||
"custom_favicon": "Favicon personalizado",
|
||||
"custom_code": "Código personalizado"
|
||||
},
|
||||
"customize-lms": {
|
||||
"title": "Personalizar LMS",
|
||||
@@ -1084,7 +1096,8 @@
|
||||
"learn": "Saber mais",
|
||||
"month": "mês",
|
||||
"monthly": "Por mês",
|
||||
"annually": "Anualmente"
|
||||
"annually": "Anualmente",
|
||||
"save": "Economize 2 meses"
|
||||
},
|
||||
"plan_names": {
|
||||
"free": "Livre",
|
||||
@@ -1206,9 +1219,9 @@
|
||||
"update_organization": "Atualizar organização",
|
||||
"team": {
|
||||
"heading": "Equipe",
|
||||
"sub_heading": "Configure seu site",
|
||||
"body": "Adicione colegas de equipe à sua organização para que ambos possam colaborar em projetos.",
|
||||
"button": "Gerenciar equipe"
|
||||
"button": "Gerenciar equipe",
|
||||
"sub_heading": "Monte sua equipe"
|
||||
},
|
||||
"custom_domain": {
|
||||
"heading": "Domínio personalizado",
|
||||
@@ -1239,7 +1252,10 @@
|
||||
},
|
||||
"profile_picture": {
|
||||
"heading": "Foto do perfil",
|
||||
"upload_image": "Enviar Imagem"
|
||||
"upload_image": "Enviar Imagem",
|
||||
"file_size": "Tamanho máximo do arquivo:",
|
||||
"accepted": "Aceito:",
|
||||
"validation_error": "O tamanho do arquivo excede o limite máximo:"
|
||||
}
|
||||
},
|
||||
"tabs": {
|
||||
|
||||
@@ -194,7 +194,8 @@
|
||||
"team_members": "Члены команды",
|
||||
"back": "Вернуться к настройкам",
|
||||
"download_lessons": "Обновите, чтобы скачивать уроки",
|
||||
"download_course": "Обновите, чтобы загрузить курс"
|
||||
"download_course": "Обновите, чтобы загрузить курс",
|
||||
"domain": "Обновите свой план, чтобы добавить персональные домены"
|
||||
},
|
||||
"audience": {
|
||||
"title": "Аудитория",
|
||||
@@ -923,12 +924,23 @@
|
||||
"settings": {
|
||||
"domains": {
|
||||
"add": "Добавлять",
|
||||
"work_together": "Добавьте своих товарищей по команде или сотрудников в свою организацию. Начните работать вместе",
|
||||
"url": "URL-адрес",
|
||||
"update": "Обновить домен",
|
||||
"custom": "Пользовательский домен",
|
||||
"domain": "Добавьте свое собственное доменное имя",
|
||||
"title": "Пользовательский домен"
|
||||
"title": "Пользовательский домен",
|
||||
"save": "Сохранять",
|
||||
"your_domain": "Ваш домен",
|
||||
"custom_domain_error": "Вы не можете добавить домен верхнего уровня в качестве личного домена. Вместо этого используйте поддомен, например «courses.[вашсайт].com» или «www.[вашсайт].com».",
|
||||
"custom_domain_success": "Персональный домен успешно добавлен",
|
||||
"dns_description": "Чтобы продолжить, установите следующую запись у своего DNS-провайдера:",
|
||||
"dns_type": "Тип",
|
||||
"dns_value": "Ценить",
|
||||
"dns_name": "Имя",
|
||||
"refresh": "Обновить",
|
||||
"remove": "Удалять",
|
||||
"custom_favicon": "Пользовательский значок",
|
||||
"custom_code": "Пользовательский код"
|
||||
},
|
||||
"customize-lms": {
|
||||
"title": "Настроить систему управления обучением",
|
||||
@@ -1084,7 +1096,8 @@
|
||||
"learn": "Узнать больше",
|
||||
"month": "месяц",
|
||||
"monthly": "Ежемесячно",
|
||||
"annually": "Ежегодно"
|
||||
"annually": "Ежегодно",
|
||||
"save": "Сэкономьте 2 месяца"
|
||||
},
|
||||
"plan_names": {
|
||||
"free": "Бесплатно",
|
||||
@@ -1206,9 +1219,9 @@
|
||||
"update_organization": "Обновить организацию",
|
||||
"team": {
|
||||
"heading": "Команда",
|
||||
"sub_heading": "Настройте свой веб-сайт",
|
||||
"body": "Добавьте коллег в свою организацию, чтобы вы оба могли совместно работать над проектами.",
|
||||
"button": "Управление командой"
|
||||
"button": "Управление командой",
|
||||
"sub_heading": "Создайте свою команду"
|
||||
},
|
||||
"custom_domain": {
|
||||
"heading": "Пользовательский домен",
|
||||
@@ -1239,7 +1252,10 @@
|
||||
},
|
||||
"profile_picture": {
|
||||
"heading": "Изображение профиля",
|
||||
"upload_image": "Загрузить изображение"
|
||||
"upload_image": "Загрузить изображение",
|
||||
"file_size": "Максимальный размер файла:",
|
||||
"accepted": "Принял:",
|
||||
"validation_error": "Размер файла превышает максимальный предел:"
|
||||
}
|
||||
},
|
||||
"tabs": {
|
||||
|
||||
@@ -194,7 +194,8 @@
|
||||
"team_members": "Thành viên của nhóm",
|
||||
"back": "Quay lại Cài đặt",
|
||||
"download_lessons": "Nâng cấp để tải bài học",
|
||||
"download_course": "Nâng cấp để tải xuống khóa học"
|
||||
"download_course": "Nâng cấp để tải xuống khóa học",
|
||||
"domain": "Nâng cấp gói của bạn để thêm miền tùy chỉnh"
|
||||
},
|
||||
"audience": {
|
||||
"title": "Khán giả",
|
||||
@@ -923,12 +924,23 @@
|
||||
"settings": {
|
||||
"domains": {
|
||||
"add": "Thêm vào",
|
||||
"work_together": "Thêm đồng đội hoặc cộng tác viên vào tổ chức của bạn. Bắt đầu làm việc cùng nhau",
|
||||
"url": "URL",
|
||||
"update": "Cập nhật tên miền",
|
||||
"custom": "Tên miền tùy chọn",
|
||||
"domain": "Thêm tên miền của riêng bạn",
|
||||
"title": "Tên miền tùy chọn"
|
||||
"title": "Tên miền tùy chọn",
|
||||
"save": "Cứu",
|
||||
"your_domain": "Tên miền của bạn",
|
||||
"custom_domain_error": "Bạn không thể thêm miền cấp cao nhất làm miền tùy chỉnh. Thay vào đó, hãy sử dụng tên miền phụ như 'courses.[yourwebsite].com' hoặc 'www.[yourwebsite].com'.",
|
||||
"custom_domain_success": "Đã thêm miền tùy chỉnh thành công",
|
||||
"dns_description": "Đặt bản ghi sau trên nhà cung cấp DNS của bạn để tiếp tục:",
|
||||
"dns_type": "Kiểu",
|
||||
"dns_value": "Giá trị",
|
||||
"dns_name": "Tên",
|
||||
"refresh": "Làm cho khỏe lại",
|
||||
"remove": "Di dời",
|
||||
"custom_favicon": "Biểu tượng yêu thích tùy chỉnh",
|
||||
"custom_code": "Mã tùy chỉnh"
|
||||
},
|
||||
"customize-lms": {
|
||||
"title": "Tùy chỉnh LMS",
|
||||
@@ -1084,7 +1096,8 @@
|
||||
"learn": "Tìm hiểu thêm",
|
||||
"month": "tháng",
|
||||
"monthly": "hàng tháng",
|
||||
"annually": "Hàng năm"
|
||||
"annually": "Hàng năm",
|
||||
"save": "Tiết kiệm 2 tháng"
|
||||
},
|
||||
"plan_names": {
|
||||
"free": "Miễn phí",
|
||||
@@ -1206,9 +1219,9 @@
|
||||
"update_organization": "Cập nhật tổ chức",
|
||||
"team": {
|
||||
"heading": "Đội",
|
||||
"sub_heading": "Thiết lập trang web của bạn",
|
||||
"body": "Thêm đồng đội vào tổ chức của bạn để cả hai có thể cộng tác trong các dự án.",
|
||||
"button": "Quản lý nhóm"
|
||||
"button": "Quản lý nhóm",
|
||||
"sub_heading": "Thiết lập nhóm của bạn"
|
||||
},
|
||||
"custom_domain": {
|
||||
"heading": "Tên miền tùy chọn",
|
||||
@@ -1239,7 +1252,10 @@
|
||||
},
|
||||
"profile_picture": {
|
||||
"heading": "Ảnh đại diện",
|
||||
"upload_image": "Tải hình ảnh lên"
|
||||
"upload_image": "Tải hình ảnh lên",
|
||||
"file_size": "Kích thước tệp tối đa:",
|
||||
"accepted": "Đã chấp nhận:",
|
||||
"validation_error": "Kích thước tệp vượt quá giới hạn tối đa:"
|
||||
}
|
||||
},
|
||||
"tabs": {
|
||||
|
||||
@@ -34,6 +34,10 @@ export interface CurrentOrg {
|
||||
is_active: boolean;
|
||||
}[];
|
||||
is_restricted: boolean;
|
||||
customDomain?: string;
|
||||
isCustomDomainVerified?: boolean;
|
||||
customCode?: string;
|
||||
favicon?: string;
|
||||
}
|
||||
|
||||
export interface OrgTeamMember {
|
||||
@@ -53,3 +57,62 @@ export interface OrgAudience {
|
||||
avatar_url: string;
|
||||
date_joined: string;
|
||||
}
|
||||
|
||||
export type DomainVerificationStatusProps =
|
||||
| 'Valid Configuration'
|
||||
| 'Invalid Configuration'
|
||||
| 'Pending Verification'
|
||||
| 'Domain Not Found'
|
||||
| 'Unknown Error';
|
||||
|
||||
// From https://vercel.com/docs/rest-api/endpoints#get-a-project-domain
|
||||
export interface DomainResponse {
|
||||
name: string;
|
||||
apexName: string;
|
||||
projectId: string;
|
||||
redirect?: string | null;
|
||||
redirectStatusCode?: (307 | 301 | 302 | 308) | null;
|
||||
gitBranch?: string | null;
|
||||
updatedAt?: number;
|
||||
createdAt?: number;
|
||||
/** `true` if the domain is verified for use with the project. If `false` it will not be used as an alias on this project until the challenge in `verification` is completed. */
|
||||
verified: boolean;
|
||||
/** A list of verification challenges, one of which must be completed to verify the domain for use on the project. After the challenge is complete `POST /projects/:idOrName/domains/:domain/verify` to verify the domain. Possible challenges: - If `verification.type = TXT` the `verification.domain` will be checked for a TXT record matching `verification.value`. */
|
||||
verification: {
|
||||
type: string;
|
||||
domain: string;
|
||||
value: string;
|
||||
reason: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
// From https://vercel.com/docs/rest-api/endpoints#get-a-domain-s-configuration
|
||||
export interface DomainConfigResponse {
|
||||
/** How we see the domain's configuration. - `CNAME`: Domain has a CNAME pointing to Vercel. - `A`: Domain's A record is resolving to Vercel. - `http`: Domain is resolving to Vercel but may be behind a Proxy. - `null`: Domain is not resolving to Vercel. */
|
||||
configuredBy?: ('CNAME' | 'A' | 'http') | null;
|
||||
/** Which challenge types the domain can use for issuing certs. */
|
||||
acceptedChallenges?: ('dns-01' | 'http-01')[];
|
||||
/** Whether or not the domain is configured AND we can automatically generate a TLS certificate. */
|
||||
misconfigured: boolean;
|
||||
}
|
||||
|
||||
// From https://vercel.com/docs/rest-api/endpoints#verify-project-domain
|
||||
export interface DomainVerificationResponse {
|
||||
name: string;
|
||||
apexName: string;
|
||||
projectId: string;
|
||||
redirect?: string | null;
|
||||
redirectStatusCode?: (307 | 301 | 302 | 308) | null;
|
||||
gitBranch?: string | null;
|
||||
updatedAt?: number;
|
||||
createdAt?: number;
|
||||
/** `true` if the domain is verified for use with the project. If `false` it will not be used as an alias on this project until the challenge in `verification` is completed. */
|
||||
verified: boolean;
|
||||
/** A list of verification challenges, one of which must be completed to verify the domain for use on the project. After the challenge is complete `POST /projects/:idOrName/domains/:domain/verify` to verify the domain. Possible challenges: - If `verification.type = TXT` the `verification.domain` will be checked for a TXT record matching `verification.value`. */
|
||||
verification?: {
|
||||
type: string;
|
||||
domain: string;
|
||||
value: string;
|
||||
reason: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
@@ -70,11 +70,19 @@ export const load = async ({ url, cookies }): Promise<LoadOutput> => {
|
||||
const isDev = dev || isLocalHost;
|
||||
|
||||
if (!url.host.includes('.classroomio.com') && !isLocalHost) {
|
||||
// TODO: We can verify if custom domain here
|
||||
return response;
|
||||
}
|
||||
// Custom domain
|
||||
response.org = (await getCurrentOrg(url.host, true, true)) || null;
|
||||
|
||||
if (!blockedSubdomain.includes(subdomain)) {
|
||||
console.log('custom domain response.org', response.org);
|
||||
|
||||
if (!response.org) {
|
||||
throw redirect(307, 'https://app.classroomio.com/404?type=org');
|
||||
}
|
||||
|
||||
response.isOrgSite = true;
|
||||
response.orgSiteName = response.org?.siteName || '';
|
||||
return response;
|
||||
} else if (!blockedSubdomain.includes(subdomain)) {
|
||||
const answer = !!subdomain;
|
||||
|
||||
response.isOrgSite = debugMode || answer;
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||
import {
|
||||
getConfigResponse,
|
||||
addDomainToVercel,
|
||||
removeDomainFromVercelProject
|
||||
} from '$lib/utils/functions/domains';
|
||||
|
||||
const supabase = getSupabase();
|
||||
|
||||
export async function POST({ request }) {
|
||||
const { params } = await request.json();
|
||||
const accessToken = request.headers.get('Authorization');
|
||||
console.log('/GET api/domain', params);
|
||||
|
||||
if (!params?.key || !accessToken) {
|
||||
return json({ success: false, message: 'Missing fields' }, { status: 400 });
|
||||
}
|
||||
|
||||
let user;
|
||||
try {
|
||||
const { data } = await supabase.auth.getUser(accessToken);
|
||||
user = data.user;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
switch (params.key) {
|
||||
case 'verify_domain': {
|
||||
const configResponse = await getConfigResponse(params.domain);
|
||||
|
||||
return json({ success: true, verified: !configResponse.misconfigured }, { status: 200 });
|
||||
}
|
||||
case 'add_domain': {
|
||||
const addDomainResponse = await addDomainToVercel(params.domain);
|
||||
return json({ success: true, data: addDomainResponse }, { status: 200 });
|
||||
}
|
||||
case 'remove_domain': {
|
||||
const removeDomainResponse = await removeDomainFromVercelProject(params.domain);
|
||||
return json({ success: true, data: removeDomainResponse }, { status: 200 });
|
||||
}
|
||||
default:
|
||||
return json({ success: false, message: 'Invalid key' }, { status: 400 });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return json({ success: false, message: 'Internal server error' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
Generated
+49
-76
@@ -167,7 +167,7 @@ importers:
|
||||
version: 5.1.6
|
||||
vite:
|
||||
specifier: ^4.4.2
|
||||
version: 4.4.8(@types/node@22.5.1)(sass@1.64.2)
|
||||
version: 4.4.8(@types/node@18.11.10)(sass@1.64.2)
|
||||
|
||||
apps/dashboard:
|
||||
dependencies:
|
||||
@@ -249,6 +249,9 @@ importers:
|
||||
html-to-image:
|
||||
specifier: ^1.11.11
|
||||
version: 1.11.11
|
||||
is-valid-domain:
|
||||
specifier: ^0.1.6
|
||||
version: 0.1.6
|
||||
js-yaml:
|
||||
specifier: ^4.1.0
|
||||
version: 4.1.0
|
||||
@@ -294,6 +297,9 @@ importers:
|
||||
sveltekit-i18n:
|
||||
specifier: ^2.4.2
|
||||
version: 2.4.2(svelte@4.1.2)
|
||||
tldts:
|
||||
specifier: ^6.1.41
|
||||
version: 6.1.41
|
||||
unsplash-js:
|
||||
specifier: ^7.0.18
|
||||
version: 7.0.18
|
||||
@@ -369,7 +375,7 @@ importers:
|
||||
version: 3.1.1
|
||||
jest:
|
||||
specifier: ^29.7.0
|
||||
version: 29.7.0(@types/node@22.5.1)(ts-node@10.9.2)
|
||||
version: 29.7.0(@types/node@18.11.10)(ts-node@10.9.2)
|
||||
postcss:
|
||||
specifier: ^8.4.24
|
||||
version: 8.4.24
|
||||
@@ -408,7 +414,7 @@ importers:
|
||||
version: 29.1.1(@babel/core@7.22.9)(babel-jest@29.7.0)(jest@29.7.0)(typescript@5.1.6)
|
||||
ts-node:
|
||||
specifier: ^10.9.2
|
||||
version: 10.9.2(@types/node@22.5.1)(typescript@5.1.6)
|
||||
version: 10.9.2(@types/node@18.11.10)(typescript@5.1.6)
|
||||
tsconfig:
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/tsconfig
|
||||
@@ -420,7 +426,7 @@ importers:
|
||||
version: 5.1.6
|
||||
vite:
|
||||
specifier: ^4.4.8
|
||||
version: 4.4.8(@types/node@22.5.1)(sass@1.64.2)
|
||||
version: 4.4.8(@types/node@18.11.10)(sass@1.64.2)
|
||||
|
||||
apps/docs:
|
||||
dependencies:
|
||||
@@ -5831,7 +5837,7 @@ packages:
|
||||
sirv: 2.0.3
|
||||
svelte: 4.1.2
|
||||
undici: 5.22.1
|
||||
vite: 4.4.8(@types/node@22.5.1)(sass@1.64.2)
|
||||
vite: 4.4.8(@types/node@18.11.10)(sass@1.64.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
@@ -5859,7 +5865,7 @@ packages:
|
||||
svelte: 4.1.2
|
||||
tiny-glob: 0.2.9
|
||||
undici: 5.26.5
|
||||
vite: 4.4.8(@types/node@22.5.1)(sass@1.64.2)
|
||||
vite: 4.4.8(@types/node@18.11.10)(sass@1.64.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -5874,7 +5880,7 @@ packages:
|
||||
'@sveltejs/vite-plugin-svelte': 2.5.3(svelte@4.1.2)(vite@4.4.8)
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
svelte: 4.1.2
|
||||
vite: 4.4.8(@types/node@22.5.1)(sass@1.64.2)
|
||||
vite: 4.4.8(@types/node@18.11.10)(sass@1.64.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -5892,7 +5898,7 @@ packages:
|
||||
magic-string: 0.30.5
|
||||
svelte: 4.1.2
|
||||
svelte-hmr: 0.15.3(svelte@4.1.2)
|
||||
vite: 4.4.8(@types/node@22.5.1)(sass@1.64.2)
|
||||
vite: 4.4.8(@types/node@18.11.10)(sass@1.64.2)
|
||||
vitefu: 0.2.5(vite@4.4.8)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -5975,7 +5981,7 @@ packages:
|
||||
chalk: 3.0.0
|
||||
css.escape: 1.5.1
|
||||
dom-accessibility-api: 0.5.16
|
||||
jest: 29.7.0(@types/node@22.5.1)(ts-node@10.9.2)
|
||||
jest: 29.7.0(@types/node@18.11.10)(ts-node@10.9.2)
|
||||
lodash: 4.17.21
|
||||
redent: 3.0.0
|
||||
dev: true
|
||||
@@ -6188,11 +6194,6 @@ packages:
|
||||
/@types/node@18.11.10:
|
||||
resolution: {integrity: sha512-juG3RWMBOqcOuXC643OAdSA525V44cVgGV6dUDuiFtss+8Fk5x1hI93Rsld43VeJVIeqlP9I7Fn9/qaVqoEAuQ==}
|
||||
|
||||
/@types/node@22.5.1:
|
||||
resolution: {integrity: sha512-KkHsxej0j9IW1KKOOAA/XBA0z08UFSrRQHErzEfA3Vgq57eXIMYboIlHJuYIfd+lwCQjtKqUu3UnmKbtUc9yRw==}
|
||||
dependencies:
|
||||
undici-types: 6.19.8
|
||||
|
||||
/@types/nodemailer@6.4.11:
|
||||
resolution: {integrity: sha512-Ld2c0frwpGT4VseuoeboCXQ7UJIkK3X7Lx/4YsZEiUHtHsthWAOCYtf6PAiLhMtfwV0cWJRabLBS3+LD8x6Nrw==}
|
||||
dependencies:
|
||||
@@ -8233,7 +8234,7 @@ packages:
|
||||
path-type: 4.0.0
|
||||
dev: false
|
||||
|
||||
/create-jest@29.7.0(@types/node@22.5.1)(ts-node@10.9.2):
|
||||
/create-jest@29.7.0(@types/node@18.11.10)(ts-node@10.9.2):
|
||||
resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
hasBin: true
|
||||
@@ -8242,7 +8243,7 @@ packages:
|
||||
chalk: 4.1.2
|
||||
exit: 0.1.2
|
||||
graceful-fs: 4.2.11
|
||||
jest-config: 29.7.0(@types/node@22.5.1)(ts-node@10.9.2)
|
||||
jest-config: 29.7.0(@types/node@18.11.10)(ts-node@10.9.2)
|
||||
jest-util: 29.7.0
|
||||
prompts: 2.4.2
|
||||
transitivePeerDependencies:
|
||||
@@ -11497,6 +11498,12 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/is-valid-domain@0.1.6:
|
||||
resolution: {integrity: sha512-ZKtq737eFkZr71At8NxOFcP9O1K89gW3DkdrGMpp1upr/ueWjj+Weh4l9AI4rN0Gt8W2M1w7jrG2b/Yv83Ljpg==}
|
||||
dependencies:
|
||||
punycode: 2.3.1
|
||||
dev: false
|
||||
|
||||
/is-weakmap@2.0.1:
|
||||
resolution: {integrity: sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==}
|
||||
dev: true
|
||||
@@ -11665,7 +11672,7 @@ packages:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/jest-cli@29.7.0(@types/node@22.5.1)(ts-node@10.9.2):
|
||||
/jest-cli@29.7.0(@types/node@18.11.10)(ts-node@10.9.2):
|
||||
resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
hasBin: true
|
||||
@@ -11679,10 +11686,10 @@ packages:
|
||||
'@jest/test-result': 29.7.0
|
||||
'@jest/types': 29.6.3
|
||||
chalk: 4.1.2
|
||||
create-jest: 29.7.0(@types/node@22.5.1)(ts-node@10.9.2)
|
||||
create-jest: 29.7.0(@types/node@18.11.10)(ts-node@10.9.2)
|
||||
exit: 0.1.2
|
||||
import-local: 3.1.0
|
||||
jest-config: 29.7.0(@types/node@22.5.1)(ts-node@10.9.2)
|
||||
jest-config: 29.7.0(@types/node@18.11.10)(ts-node@10.9.2)
|
||||
jest-util: 29.7.0
|
||||
jest-validate: 29.7.0
|
||||
yargs: 17.7.2
|
||||
@@ -11728,48 +11735,7 @@ packages:
|
||||
pretty-format: 29.7.0
|
||||
slash: 3.0.0
|
||||
strip-json-comments: 3.1.1
|
||||
ts-node: 10.9.2(@types/node@22.5.1)(typescript@5.1.6)
|
||||
transitivePeerDependencies:
|
||||
- babel-plugin-macros
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/jest-config@29.7.0(@types/node@22.5.1)(ts-node@10.9.2):
|
||||
resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
peerDependencies:
|
||||
'@types/node': '*'
|
||||
ts-node: '>=9.0.0'
|
||||
peerDependenciesMeta:
|
||||
'@types/node':
|
||||
optional: true
|
||||
ts-node:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/core': 7.23.3
|
||||
'@jest/test-sequencer': 29.7.0
|
||||
'@jest/types': 29.6.3
|
||||
'@types/node': 22.5.1
|
||||
babel-jest: 29.7.0(@babel/core@7.23.3)
|
||||
chalk: 4.1.2
|
||||
ci-info: 3.9.0
|
||||
deepmerge: 4.3.1
|
||||
glob: 7.2.3
|
||||
graceful-fs: 4.2.11
|
||||
jest-circus: 29.7.0
|
||||
jest-environment-node: 29.7.0
|
||||
jest-get-type: 29.6.3
|
||||
jest-regex-util: 29.6.3
|
||||
jest-resolve: 29.7.0
|
||||
jest-runner: 29.7.0
|
||||
jest-util: 29.7.0
|
||||
jest-validate: 29.7.0
|
||||
micromatch: 4.0.5
|
||||
parse-json: 5.2.0
|
||||
pretty-format: 29.7.0
|
||||
slash: 3.0.0
|
||||
strip-json-comments: 3.1.1
|
||||
ts-node: 10.9.2(@types/node@22.5.1)(typescript@5.1.6)
|
||||
ts-node: 10.9.2(@types/node@18.11.10)(typescript@5.1.6)
|
||||
transitivePeerDependencies:
|
||||
- babel-plugin-macros
|
||||
- supports-color
|
||||
@@ -12058,7 +12024,7 @@ packages:
|
||||
supports-color: 8.1.1
|
||||
dev: true
|
||||
|
||||
/jest@29.7.0(@types/node@22.5.1)(ts-node@10.9.2):
|
||||
/jest@29.7.0(@types/node@18.11.10)(ts-node@10.9.2):
|
||||
resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
hasBin: true
|
||||
@@ -12071,7 +12037,7 @@ packages:
|
||||
'@jest/core': 29.7.0(ts-node@10.9.2)
|
||||
'@jest/types': 29.6.3
|
||||
import-local: 3.1.0
|
||||
jest-cli: 29.7.0(@types/node@22.5.1)(ts-node@10.9.2)
|
||||
jest-cli: 29.7.0(@types/node@18.11.10)(ts-node@10.9.2)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- babel-plugin-macros
|
||||
@@ -14335,7 +14301,7 @@ packages:
|
||||
dependencies:
|
||||
lilconfig: 2.1.0
|
||||
postcss: 8.4.24
|
||||
ts-node: 10.9.2(@types/node@22.5.1)(typescript@5.1.6)
|
||||
ts-node: 10.9.2(@types/node@18.11.10)(typescript@5.1.6)
|
||||
yaml: 2.3.4
|
||||
dev: true
|
||||
|
||||
@@ -14353,7 +14319,7 @@ packages:
|
||||
dependencies:
|
||||
lilconfig: 2.1.0
|
||||
postcss: 8.4.32
|
||||
ts-node: 10.9.2(@types/node@22.5.1)(typescript@5.1.6)
|
||||
ts-node: 10.9.2(@types/node@18.11.10)(typescript@5.1.6)
|
||||
yaml: 2.3.4
|
||||
|
||||
/postcss-nested@6.0.1(postcss@8.4.32):
|
||||
@@ -14692,7 +14658,6 @@ packages:
|
||||
/punycode@2.3.1:
|
||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/puppeteer-core@20.9.0(typescript@5.3.2):
|
||||
resolution: {integrity: sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==}
|
||||
@@ -16146,7 +16111,7 @@ packages:
|
||||
jest: '>= 27'
|
||||
svelte: '>= 3'
|
||||
dependencies:
|
||||
jest: 29.7.0(@types/node@22.5.1)(ts-node@10.9.2)
|
||||
jest: 29.7.0(@types/node@18.11.10)(ts-node@10.9.2)
|
||||
svelte: 4.1.2
|
||||
dev: true
|
||||
|
||||
@@ -16575,6 +16540,17 @@ packages:
|
||||
engines: {node: '>=12'}
|
||||
dev: true
|
||||
|
||||
/tldts-core@6.1.41:
|
||||
resolution: {integrity: sha512-SkwZgo1ZzMp2ziMBwci5VBnLR9VywCi02jSgMX5TO5kf9fdaBsxZkblLff3NlJNTcH0vfvEsgw2B7jVR556Vgw==}
|
||||
dev: false
|
||||
|
||||
/tldts@6.1.41:
|
||||
resolution: {integrity: sha512-RNpUkL5fYD2DTQQCdr8QMDp6UL0ThtpXT3q3+qPE05dIT+RK2I3M0VByVbQN1dEhLUGzimivVwxK2By9epLk6w==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
tldts-core: 6.1.41
|
||||
dev: false
|
||||
|
||||
/tmp@0.2.3:
|
||||
resolution: {integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==}
|
||||
engines: {node: '>=14.14'}
|
||||
@@ -16700,7 +16676,7 @@ packages:
|
||||
babel-jest: 29.7.0(@babel/core@7.22.9)
|
||||
bs-logger: 0.2.6
|
||||
fast-json-stable-stringify: 2.1.0
|
||||
jest: 29.7.0(@types/node@22.5.1)(ts-node@10.9.2)
|
||||
jest: 29.7.0(@types/node@18.11.10)(ts-node@10.9.2)
|
||||
jest-util: 29.7.0
|
||||
json5: 2.2.3
|
||||
lodash.memoize: 4.1.2
|
||||
@@ -16710,7 +16686,7 @@ packages:
|
||||
yargs-parser: 21.1.1
|
||||
dev: true
|
||||
|
||||
/ts-node@10.9.2(@types/node@22.5.1)(typescript@5.1.6):
|
||||
/ts-node@10.9.2(@types/node@18.11.10)(typescript@5.1.6):
|
||||
resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -16729,7 +16705,7 @@ packages:
|
||||
'@tsconfig/node12': 1.0.11
|
||||
'@tsconfig/node14': 1.0.3
|
||||
'@tsconfig/node16': 1.0.4
|
||||
'@types/node': 22.5.1
|
||||
'@types/node': 18.11.10
|
||||
acorn: 8.11.2
|
||||
acorn-walk: 8.3.1
|
||||
arg: 4.1.3
|
||||
@@ -16988,9 +16964,6 @@ packages:
|
||||
resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==}
|
||||
dev: false
|
||||
|
||||
/undici-types@6.19.8:
|
||||
resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
|
||||
|
||||
/undici@5.22.1:
|
||||
resolution: {integrity: sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==}
|
||||
engines: {node: '>=14.0'}
|
||||
@@ -17390,7 +17363,7 @@ packages:
|
||||
vfile-message: 4.0.2
|
||||
dev: false
|
||||
|
||||
/vite@4.4.8(@types/node@22.5.1)(sass@1.64.2):
|
||||
/vite@4.4.8(@types/node@18.11.10)(sass@1.64.2):
|
||||
resolution: {integrity: sha512-LONawOUUjxQridNWGQlNizfKH89qPigK36XhMI7COMGztz8KNY0JHim7/xDd71CZwGT4HtSRgI7Hy+RlhG0Gvg==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
hasBin: true
|
||||
@@ -17418,7 +17391,7 @@ packages:
|
||||
terser:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/node': 22.5.1
|
||||
'@types/node': 18.11.10
|
||||
esbuild: 0.18.20
|
||||
postcss: 8.4.32
|
||||
rollup: 3.29.4
|
||||
@@ -17434,7 +17407,7 @@ packages:
|
||||
vite:
|
||||
optional: true
|
||||
dependencies:
|
||||
vite: 4.4.8(@types/node@22.5.1)(sass@1.64.2)
|
||||
vite: 4.4.8(@types/node@18.11.10)(sass@1.64.2)
|
||||
|
||||
/vscode-oniguruma@1.7.0:
|
||||
resolution: {integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
alter table "public"."organization" add column "customCode" text;
|
||||
|
||||
alter table "public"."organization" add column "customDomain" text;
|
||||
|
||||
alter table "public"."organization" add column "favicon" text;
|
||||
|
||||
alter table "public"."organization" add column "isCustomDomainVerified" boolean default false;
|
||||
|
||||
CREATE UNIQUE INDEX "organization_customDomain_key" ON public.organization USING btree ("customDomain");
|
||||
|
||||
alter table "public"."organization" add constraint "organization_customDomain_key" UNIQUE using index "organization_customDomain_key";
|
||||
Reference in New Issue
Block a user