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:
@@ -3,7 +3,7 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="h-[70px] w-full border border-gray-100 dark:bg-neutral-800 mx-4 my-2 rounded-md transition ease-in-out relative flex pt-3 px-3"
|
||||
class="h-[70px] w-full border border-gray-100 dark:border-neutral-600 dark:bg-neutral-800 mx-4 my-2 rounded-md transition ease-in-out relative flex pt-3 px-3"
|
||||
>
|
||||
<div class="w-10 flex justify-evenly flex-col px-1 mr-2 rounded-md">
|
||||
<SkeletonPlaceholder style="width: 100%; height: 40%; border-radius: 4px;" />
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="root w-full border border-gray-100 dark:bg-neutral-800 mr-4 mb-4 rounded-md transition ease-in-out relative flex p-3"
|
||||
class="root w-full border border-gray-100 dark:border-neutral-600 dark:bg-neutral-800 mr-4 mb-4 rounded-md transition ease-in-out relative flex p-3"
|
||||
>
|
||||
<SkeletonPlaceholder style="width: 20%; height: 100%;" />
|
||||
<div class="w-full mx-5 flex justify-evenly flex-col">
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<script>
|
||||
import { Grid, Row, Column } from 'carbon-components-svelte';
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
||||
import { currentOrgPlan, isFreePlan } from '$lib/utils/store/org';
|
||||
import SectionTitle from '../SectionTitle.svelte';
|
||||
|
||||
let isRedirecting = false;
|
||||
|
||||
async function onOpenBilling() {
|
||||
console.log({
|
||||
plan: $currentOrgPlan
|
||||
});
|
||||
|
||||
if ($currentOrgPlan?.subscriptionId) {
|
||||
isRedirecting = true;
|
||||
fetch('/api/lmz/subscriptions?subscriptionId=' + $currentOrgPlan?.subscriptionId)
|
||||
.then((response) => response.json())
|
||||
.then((res) => {
|
||||
isRedirecting = false;
|
||||
window.open(res.customerPortal, '_blank');
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<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={8} md={8} lg={8}>
|
||||
<h4 class="dark:text-white lg:mt-0">Manage your billing</h4>
|
||||
{#if $isFreePlan}
|
||||
You don't have any active billing
|
||||
{:else}
|
||||
<p class="mb-3">We use Lemonsqueezy to help manage your billing</p>
|
||||
|
||||
<PrimaryButton
|
||||
label="Open billing"
|
||||
variant={VARIANTS.CONTAINED_DARK}
|
||||
onClick={onOpenBilling}
|
||||
isLoading={isRedirecting}
|
||||
/>
|
||||
{/if}
|
||||
</Column>
|
||||
</Row>
|
||||
</Grid>
|
||||
@@ -72,7 +72,7 @@
|
||||
$: setSiteName($currentOrg.siteName);
|
||||
</script>
|
||||
|
||||
<Grid class="border rounded border-gray-200 w-full mt-5">
|
||||
<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={6} lg={8}>
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Grid class="border-c rounded border-gray-200 w-full mt-5">
|
||||
<Grid class="border-c rounded border-gray-200 dark:border-neutral-600 w-full mt-5">
|
||||
<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" />
|
||||
|
||||
@@ -117,7 +117,7 @@
|
||||
$: setDefault($currentOrg?.landingpage as OrgLandingPageJson);
|
||||
</script>
|
||||
|
||||
<Grid class="border-c rounded border-gray-200 w-full mt-5 relative">
|
||||
<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>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script>
|
||||
import { goto } from '$app/navigation';
|
||||
import { Grid, Row, Column } from 'carbon-components-svelte';
|
||||
import FlashFilled from 'carbon-icons-svelte/lib/FlashFilled.svelte';
|
||||
import TextField from '$lib/components/Form/TextField.svelte';
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
||||
@@ -9,6 +10,7 @@
|
||||
import { snackbar } from '$lib/components/Snackbar/store';
|
||||
import { currentOrg, currentOrgPath } from '$lib/utils/store/org';
|
||||
import SectionTitle from '../SectionTitle.svelte';
|
||||
import { isFreePlan } from '$lib/utils/store/org';
|
||||
|
||||
let avatar;
|
||||
let loading = false;
|
||||
@@ -94,7 +96,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Grid class="border-c rounded border-gray-200 w-full mt-5">
|
||||
<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={8} md={8} lg={8} class="mt-2 lg:mt-0 flex flex-col items-center lg:items-start">
|
||||
@@ -175,7 +177,7 @@
|
||||
</p>
|
||||
<PrimaryButton
|
||||
label="Edit domain"
|
||||
className="my-7 py-5 px-10 text-primary-700"
|
||||
className="my-7 py-5 px-10"
|
||||
variant={VARIANTS.OUTLINED}
|
||||
onClick={() => gotoSetting('/domains')}
|
||||
/>
|
||||
@@ -189,11 +191,15 @@
|
||||
Add team mates to your organization so you can both collaborate on projects.
|
||||
</p>
|
||||
<PrimaryButton
|
||||
label="Manage Team"
|
||||
className="my-7 py-5 px-10 text-primary-700"
|
||||
className="my-7 py-5 px-10 flex items-center gap-2 justify-center"
|
||||
variant={VARIANTS.OUTLINED}
|
||||
onClick={() => gotoSetting('/teams')}
|
||||
/>
|
||||
>
|
||||
{#if $isFreePlan}
|
||||
<FlashFilled size={16} class="text-blue-700" />
|
||||
{/if}
|
||||
Manage Team
|
||||
</PrimaryButton>
|
||||
</Column>
|
||||
</Row>
|
||||
</Grid>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import TextField from '$lib/components/Form/TextField.svelte';
|
||||
import SectionTitle from '../SectionTitle.svelte';
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
||||
import UploadImage from '$lib/components/UploadImage/index.svelte';
|
||||
import { supabase } from '$lib/utils/functions/supabase';
|
||||
import { profile } from '$lib/utils/store/user';
|
||||
@@ -61,7 +62,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Grid class="border-c rounded border-gray-200 w-full mt-5">
|
||||
<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>
|
||||
@@ -83,6 +84,7 @@
|
||||
<Row class="m-5 w-full flex items-center gap-2 lg:justify-center">
|
||||
<PrimaryButton
|
||||
label="Update Profile"
|
||||
variant={VARIANTS.CONTAINED_DARK}
|
||||
className="mr-5"
|
||||
isLoading={loading}
|
||||
isDisabled={loading}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
triggerSendEmail,
|
||||
NOTIFICATION_NAME
|
||||
} from '$lib/utils/services/notification/notification';
|
||||
import { isFreePlan } from '$lib/utils/store/org';
|
||||
import type { OrgTeamMember } from '$lib/utils/types/org';
|
||||
|
||||
let emailsStr = '';
|
||||
@@ -132,7 +133,7 @@
|
||||
$: fetchTeam($currentOrg.id);
|
||||
</script>
|
||||
|
||||
<Grid class="border rounded border-gray-200 w-full mt-5">
|
||||
<Grid class="border rounded border-gray-200 dark:border-neutral-600 w-full mt-5 relative">
|
||||
<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={6} lg={8}>
|
||||
@@ -146,10 +147,11 @@
|
||||
placeholder="email comma separated"
|
||||
bind:value={emailsStr}
|
||||
className="mb-3"
|
||||
isDisabled={$isFreePlan}
|
||||
{errorMessage}
|
||||
/>
|
||||
|
||||
<Select labelText="Role" bind:selected={role} class="mb-5 w-40">
|
||||
<Select labelText="Role" bind:selected={role} class="mb-5 w-40" disabled={$isFreePlan}>
|
||||
<!-- <SelectItem value={ROLE.ADMIN} text={ROLE_LABEL[ROLE.ADMIN]} /> -->
|
||||
<SelectItem value={ROLE.TUTOR} text={ROLE_LABEL[ROLE.TUTOR]} />
|
||||
</Select>
|
||||
@@ -158,7 +160,7 @@
|
||||
label="Send Invite"
|
||||
onClick={onSendInvite}
|
||||
{isLoading}
|
||||
isDisabled={isLoading}
|
||||
isDisabled={isLoading || $isFreePlan}
|
||||
/>
|
||||
</div>
|
||||
</Column>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import LandingpageSettings from './LandingpageSettings.svelte';
|
||||
// import Account from './Account.svelte';
|
||||
import OrgSettings from './OrgSettings.svelte';
|
||||
import Billing from './Billing.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { browser } from '$app/environment';
|
||||
import { goto } from '$app/navigation';
|
||||
@@ -64,6 +65,13 @@
|
||||
},
|
||||
{
|
||||
key: 3,
|
||||
label: 'Billing',
|
||||
tabKey: 'billing',
|
||||
href: `${$page.url.pathname}?tab=billing`,
|
||||
disabled: !$isOrgAdmin
|
||||
},
|
||||
{
|
||||
key: 4,
|
||||
label: 'Integrations',
|
||||
tabKey: 'integrations',
|
||||
href: `${$page.url.pathname}?tab=integrations`,
|
||||
@@ -91,6 +99,9 @@
|
||||
<TabContent class="w-full p-0">
|
||||
<LandingpageSettings />
|
||||
</TabContent>
|
||||
<TabContent class="w-full p-0">
|
||||
<Billing />
|
||||
</TabContent>
|
||||
<TabContent class="w-full p-0">
|
||||
<Integrations />
|
||||
</TabContent>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
import { page } from '$app/stores';
|
||||
import HelpIcon from 'carbon-icons-svelte/lib/Help.svelte';
|
||||
import ForumIcon from 'carbon-icons-svelte/lib/Forum.svelte';
|
||||
|
||||
import TextChip from '$lib/components/Chip/Text.svelte';
|
||||
import { SettingsAdjust } from 'carbon-icons-svelte';
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import OrgSelector from '$lib/components/OrgSelector/OrgSelector.svelte';
|
||||
import HomeIcon from '$lib/components/Icons/HomeIcon.svelte';
|
||||
import CourseIcon from '$lib/components/Icons/CourseIcon.svelte';
|
||||
@@ -11,37 +11,18 @@
|
||||
import SiteSettingsIcon from '$lib/components/Icons/SiteSettingsIcon.svelte';
|
||||
import AudienceIcon from '$lib/components/Icons/AudienceIcon.svelte';
|
||||
import Avatar from '$lib/components/Avatar/index.svelte';
|
||||
import { currentOrg, currentOrgPath } from '$lib/utils/store/org';
|
||||
import { currentOrgPath, currentOrgPlan, isFreePlan } from '$lib/utils/store/org';
|
||||
import { isOrgAdmin } from '$lib/utils/store/org';
|
||||
import { profile } from '$lib/utils/store/user';
|
||||
import { NavClasses } from '$lib/utils/constants/reusableClass';
|
||||
import { sideBar } from './store';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
path: '',
|
||||
label: 'Dashboard'
|
||||
},
|
||||
// {
|
||||
// path: '/quiz',
|
||||
// label: 'Quizzes'
|
||||
// },
|
||||
{
|
||||
path: '/courses',
|
||||
label: 'Courses'
|
||||
},
|
||||
{
|
||||
path: '/community',
|
||||
label: 'Community'
|
||||
},
|
||||
// {
|
||||
// path: '/site',
|
||||
// label: 'Site settings',
|
||||
// },
|
||||
{
|
||||
path: '/audience',
|
||||
label: 'Audience'
|
||||
}
|
||||
];
|
||||
let menuItems: {
|
||||
path: string;
|
||||
label: string;
|
||||
show: boolean;
|
||||
}[] = [];
|
||||
|
||||
function isActive(pagePath: string, itemPath: string) {
|
||||
const pageLinkItems = pagePath.split('/');
|
||||
@@ -56,87 +37,137 @@
|
||||
const toggleSidebar = () => {
|
||||
$sideBar.hidden = !$sideBar.hidden;
|
||||
};
|
||||
|
||||
const openModal = () => {
|
||||
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
|
||||
}
|
||||
];
|
||||
}
|
||||
</script>
|
||||
|
||||
<aside
|
||||
class={`${
|
||||
$sideBar.hidden
|
||||
? '-translate-x-[100%] absolute md:translate-x-0 md:relative z-40'
|
||||
: 'translate-x-0 absolute md:relative z-40'
|
||||
} overflow-y-auto transition w-[250px] min-w-[250px] bg-gray-100 dark:bg-neutral-900 h-[calc(100vh-48px)] border border-l-0 border-t-0 border-b-0 border-r-1`}
|
||||
? 'absolute z-40 -translate-x-[100%] md:relative md:translate-x-0'
|
||||
: 'absolute z-40 translate-x-0 md:relative'
|
||||
} border-r-1 h-[calc(100vh-48px)] w-[250px] min-w-[250px] overflow-y-auto border border-b-0 border-l-0 border-t-0 border-gray-100 dark:border-neutral-600 bg-gray-100 transition dark:bg-neutral-900`}
|
||||
>
|
||||
<div class="h-full flex flex-col">
|
||||
<div class="border-b border-gray-200 pt-5 px-4">
|
||||
{#if $currentOrg.avatar_url && $currentOrg.name}
|
||||
<Avatar
|
||||
src={$currentOrg.avatar_url}
|
||||
name={$currentOrg.name}
|
||||
shape="rounded-md"
|
||||
width="w-7"
|
||||
height="h-7"
|
||||
/>
|
||||
{:else if $currentOrg.shortName}
|
||||
<TextChip
|
||||
value={$currentOrg.shortName}
|
||||
className="bg-primary-200 dark:text-black font-bold"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<div class="flex h-full flex-col">
|
||||
<div class="">
|
||||
<OrgSelector />
|
||||
|
||||
<ul class="my-5">
|
||||
<ul class="mt-4 my-2 px-4">
|
||||
{#each menuItems as menuItem}
|
||||
<a
|
||||
href="{$currentOrgPath}{menuItem.path}"
|
||||
class="text-black no-underline"
|
||||
on:click={toggleSidebar}
|
||||
>
|
||||
<li
|
||||
class="flex items-center py-3 px-4 mb-2 {NavClasses.item} {isActive(
|
||||
$page.url.pathname,
|
||||
`${$currentOrgPath}${menuItem.path}`
|
||||
)
|
||||
? NavClasses.active
|
||||
: 'dark:text-white'}"
|
||||
{#if menuItem.show}
|
||||
<a
|
||||
href="{$currentOrgPath}{menuItem.path}"
|
||||
class="text-black no-underline"
|
||||
on:click={toggleSidebar}
|
||||
>
|
||||
{#if menuItem.path === ''}
|
||||
<HomeIcon />
|
||||
{:else if menuItem.path === '/courses'}
|
||||
<CourseIcon />
|
||||
{:else if menuItem.path === '/site'}
|
||||
<SiteSettingsIcon />
|
||||
{:else if menuItem.path === '/community'}
|
||||
<ForumIcon size={24} class="carbon-icon dark:fill-[#fff] fill-[#000]" />
|
||||
{:else if menuItem.path === '/quiz'}
|
||||
<QuizIcon />
|
||||
{:else if menuItem.path === '/audience'}
|
||||
<AudienceIcon />
|
||||
{/if}
|
||||
<p class=" ml-2">{menuItem.label}</p>
|
||||
</li>
|
||||
</a>
|
||||
<li
|
||||
class="mb-1 flex items-center gap-2.5 px-2.5 py-2 {NavClasses.item} {isActive(
|
||||
$page.url.pathname,
|
||||
`${$currentOrgPath}${menuItem.path}`
|
||||
)
|
||||
? NavClasses.active
|
||||
: 'dark:text-white'}"
|
||||
>
|
||||
{#if menuItem.path === ''}
|
||||
<HomeIcon />
|
||||
{:else if menuItem.path === '/courses'}
|
||||
<CourseIcon />
|
||||
{:else if menuItem.path === '/site'}
|
||||
<SiteSettingsIcon />
|
||||
{:else if menuItem.path === '/community'}
|
||||
<ForumIcon size={20} class="carbon-icon fill-[#000] dark:fill-[#fff]" />
|
||||
{:else if menuItem.path === '/quiz'}
|
||||
<QuizIcon />
|
||||
{:else if menuItem.path === '/audience'}
|
||||
<AudienceIcon />
|
||||
{:else if menuItem.path === '/setup'}
|
||||
<SettingsAdjust />
|
||||
{/if}
|
||||
<p class="text-sm font-medium">{menuItem.label}</p>
|
||||
</li>
|
||||
</a>
|
||||
{/if}
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
<span class="flex-grow" />
|
||||
<ul class="my-5 pb-5 px-4">
|
||||
|
||||
{#if $isFreePlan}
|
||||
<div
|
||||
class="border-primary-700 mx-4 flex flex-col items-center justify-center gap-4 rounded-md border px-2 py-6 text-center hover:scale-95 transition-all ease-in-out"
|
||||
>
|
||||
<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>
|
||||
</span>
|
||||
<PrimaryButton label="Upgrade Now" onClick={openModal} className="font-normal" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<ul class="my-5 px-4 pb-5">
|
||||
<a href={$currentOrgPath} class="text-black no-underline" on:click={toggleSidebar}>
|
||||
<li class="flex items-center py-3 px-4 mb-2 rounded">
|
||||
<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="dark:text-white ml-2">Help</p>
|
||||
<p class="ml-2.5 dark:text-white text-sm font-medium">Help</p>
|
||||
</li>
|
||||
</a>
|
||||
<a href="{$currentOrgPath}/settings" class="text-black no-underline" on:click={toggleSidebar}>
|
||||
<li
|
||||
class="flex items-center py-3 px-4 mb-2 {NavClasses.item} {isActive(
|
||||
class="mb-2 flex items-center px-2.5 py-1.5 {NavClasses.item} {isActive(
|
||||
$page.url.pathname,
|
||||
`${$currentOrgPath}/settings`
|
||||
)
|
||||
? NavClasses.active
|
||||
: 'dark:text-white'}"
|
||||
>
|
||||
<Avatar src={$profile.avatar_url} name={$profile.username} width="w-7" height="h-7" />
|
||||
<p class="ml-2">Settings</p>
|
||||
<Avatar
|
||||
src={$profile.avatar_url}
|
||||
name={$profile.username}
|
||||
width="w-[1.2rem]"
|
||||
height="h-[1.2rem]"
|
||||
/>
|
||||
<p class="ml-2.5 text-sm font-medium">Settings</p>
|
||||
</li>
|
||||
</a>
|
||||
</ul>
|
||||
|
||||
Reference in New Issue
Block a user