feat: dashboard billing (#193)
* feat: move plans into sharable json * remove unused code * update pnpm lock * update plans.json * implement early adopter pricing * docs: update meta title and description * Finished the UI for upgrading plans * Updated the pricing plans * Updated the cards ui * use relative import * improve styling of upgrade plan card * styling improvements, lemonsqueezy setup * trigger lemonsqueezy payment and add webhook * update pnpm lock * add logout route * feat: complete billing functionality * generate lock file * make crypto false on browser * feat: adding upgrade prompts to org * chore: add upgrade icon to upload video * feat: add upgrade prompt to certificate and settings * feat: implement onboarding setup page (#229) * working on the onboarding setup * working on the onboarding page * Almost done with the onboarding UI * finished the UI of the onBoarding setup * finished the UI of the onBoarding setup * Implementing the functions to carryout the logic of updating the UI onmount if a user has carried out a certain step or not * Implementing the functions to carryout the logic of updating the UI onmount if a user has carried out a certain step or not * making some chnages * finished the onBoarding task * updated some logic * Removed the uneeded svg from the static folder * fixed isCoursePublished not returning a boolean * improved the setup link protection * chore: make menuItems reactive * improve formatting * format supabase queries --------- Co-authored-by: Rotimi Best <best@classroomio.com> * revamp setup * improve the upgrade experience * migration * feat: add lemonsqueezy customer portal --------- Co-authored-by: emmanuel <chifez1@gmail.com>
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
import { LEMON_SQUEEZY_API_KEY } from '$env/static/private';
|
||||
import { lemonSqueezySetup } from '@lemonsqueezy/lemonsqueezy.js';
|
||||
|
||||
export function configureLemonSqueezy() {
|
||||
return lemonSqueezySetup({
|
||||
apiKey: LEMON_SQUEEZY_API_KEY,
|
||||
onError: (error) => {
|
||||
throw new Error(`Lemon Squeezy API error: ${error.message}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import axios from 'axios';
|
||||
|
||||
interface SubscriptionData {
|
||||
productId: string;
|
||||
email: string;
|
||||
name: string;
|
||||
triggeredBy: string;
|
||||
orgId: string;
|
||||
}
|
||||
|
||||
interface SubscriptionResponse {
|
||||
success: boolean;
|
||||
checkoutURL: string;
|
||||
}
|
||||
|
||||
export async function subscribeToProduct(data: SubscriptionData): Promise<SubscriptionResponse> {
|
||||
const response = await axios({
|
||||
method: 'POST',
|
||||
url: '/api/lmz/subscribe',
|
||||
data
|
||||
});
|
||||
|
||||
return response.data;
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
import { goto } from '$app/navigation';
|
||||
import { supabase } from '$lib/utils/functions/supabase';
|
||||
import { orgs, currentOrg, orgAudience, orgTeam } from '$lib/utils/store/org';
|
||||
import { ROLE, ROLE_LABEL } from '$lib/utils/constants/roles';
|
||||
import type { CurrentOrg, OrgTeamMember, OrgAudience } from '$lib/utils/types/org';
|
||||
import type { CurrentOrg, OrgTeamMember } from '$lib/utils/types/org';
|
||||
import type { OrganizationPlan } from '$lib/utils/types';
|
||||
|
||||
export async function getOrgTeam(orgId: string) {
|
||||
const { data, error } = await supabase
|
||||
@@ -79,7 +79,12 @@ export async function getOrganizations(userId: string) {
|
||||
avatar_url,
|
||||
landingpage,
|
||||
theme,
|
||||
created_at
|
||||
created_at,
|
||||
organization_plan(
|
||||
plan_name,
|
||||
is_active,
|
||||
subscriptionId:lmz_data->id
|
||||
)
|
||||
)
|
||||
`
|
||||
)
|
||||
@@ -108,7 +113,8 @@ export async function getOrganizations(userId: string) {
|
||||
avatar_url: orgMember?.organization?.avatar_url,
|
||||
memberId: orgMember?.id,
|
||||
role_id: orgMember?.role_id,
|
||||
landingpage: orgMember?.organization?.landingpage
|
||||
landingpage: orgMember?.organization?.landingpage,
|
||||
organization_plan: orgMember?.organization?.organization_plan
|
||||
});
|
||||
});
|
||||
|
||||
@@ -200,7 +206,6 @@ export async function getCourseBySiteName(siteName: string) {
|
||||
}
|
||||
|
||||
export async function getCurrentOrg(siteName: string, justGet = false) {
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('organization')
|
||||
.select(
|
||||
@@ -210,14 +215,17 @@ export async function getCurrentOrg(siteName: string, justGet = false) {
|
||||
siteName,
|
||||
avatar_url,
|
||||
landingpage,
|
||||
theme
|
||||
theme,
|
||||
organization_plan(
|
||||
plan_name,
|
||||
is_active
|
||||
)
|
||||
`
|
||||
)
|
||||
.eq('siteName', siteName)
|
||||
.returns<CurrentOrg[]>();
|
||||
console.log('data =', data);
|
||||
console.log('error =', error);
|
||||
|
||||
|
||||
const isDataEmpty = !data?.[0];
|
||||
|
||||
@@ -234,3 +242,32 @@ export async function getCurrentOrg(siteName: string, justGet = false) {
|
||||
return data[0];
|
||||
}
|
||||
}
|
||||
|
||||
export async function createOrgPlan(params: {
|
||||
orgId: string;
|
||||
planName: string;
|
||||
triggeredBy: number;
|
||||
data: OrganizationPlan['lmz_data'];
|
||||
}) {
|
||||
return await supabase.from('organization_plan').insert({
|
||||
activated_at: new Date().toDateString(),
|
||||
org_id: params.orgId,
|
||||
triggered_by: params.triggeredBy,
|
||||
plan_name: params.planName,
|
||||
is_active: true,
|
||||
lmz_data: params.data
|
||||
});
|
||||
}
|
||||
|
||||
export async function cancelOrgPlan(params: { orgId: string; planName: string }) {
|
||||
return await supabase
|
||||
.from('organization_plan')
|
||||
.update({
|
||||
is_active: false,
|
||||
deactivated_at: new Date().toDateString()
|
||||
})
|
||||
.match({
|
||||
plan_name: params.planName,
|
||||
org_id: params.orgId
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user