feat: subscription changes
This commit is contained in:
parent
a1994a5eb4
commit
5114ea88f7
|
|
@ -1,4 +1,4 @@
|
|||
import { Body, Controller, Get, Param, Post } from '@nestjs/common';
|
||||
import { Body, Controller, Delete, Get, Param, Post } from '@nestjs/common';
|
||||
import { ioRedis } from '@gitroom/nestjs-libraries/redis/redis.service';
|
||||
import { ConnectIntegrationDto } from '@gitroom/nestjs-libraries/dtos/integrations/connect.integration.dto';
|
||||
import { IntegrationManager } from '@gitroom/nestjs-libraries/integrations/integration.manager';
|
||||
|
|
@ -33,6 +33,7 @@ export class IntegrationsController {
|
|||
).map((p) => ({
|
||||
name: p.name,
|
||||
id: p.id,
|
||||
disabled: p.disabled,
|
||||
picture: p.picture,
|
||||
identifier: p.providerIdentifier,
|
||||
type: p.type,
|
||||
|
|
@ -186,4 +187,34 @@ export class IntegrationsController {
|
|||
expiresIn
|
||||
);
|
||||
}
|
||||
|
||||
@Post('/disable')
|
||||
disableChannel(
|
||||
@GetOrgFromRequest() org: Organization,
|
||||
@Body('id') id: string
|
||||
) {
|
||||
return this._integrationService.disableChannel(org.id, id);
|
||||
}
|
||||
|
||||
@Post('/enable')
|
||||
enableChannel(
|
||||
@GetOrgFromRequest() org: Organization,
|
||||
@Body('id') id: string
|
||||
) {
|
||||
return this._integrationService.enableChannel(
|
||||
org.id,
|
||||
// @ts-ignore
|
||||
org.subscription.totalChannels,
|
||||
id
|
||||
);
|
||||
}
|
||||
|
||||
@Delete('/')
|
||||
deleteChannel(
|
||||
@GetOrgFromRequest() org: Organization,
|
||||
@Body('id') id: string
|
||||
) {
|
||||
// @ts-ignore
|
||||
return this._integrationService.deleteChannel(org.id, id);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import {
|
|||
Sections,
|
||||
} from '@gitroom/backend/services/auth/permissions/permissions.service';
|
||||
import {removeSubdomain} from "@gitroom/helpers/subdomain/subdomain.management";
|
||||
import {pricing} from "@gitroom/nestjs-libraries/database/prisma/subscriptions/pricing";
|
||||
|
||||
@Controller('/user')
|
||||
export class UsersController {
|
||||
|
|
@ -43,6 +44,8 @@ export class UsersController {
|
|||
...user,
|
||||
orgId: organization.id,
|
||||
// @ts-ignore
|
||||
totalChannels: organization?.subscription?.totalChannels || pricing.FREE.channel,
|
||||
// @ts-ignore
|
||||
tier: organization?.subscription?.subscriptionTier || 'FREE',
|
||||
// @ts-ignore
|
||||
role: organization?.users[0]?.role,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
"use client";
|
||||
'use client';
|
||||
|
||||
import { useCallback } from 'react';
|
||||
import {useCallback, useEffect, useState} from 'react';
|
||||
import { NoBillingComponent } from '@gitroom/frontend/components/billing/no.billing.component';
|
||||
import useSWR from 'swr';
|
||||
import { LoadingComponent } from '@gitroom/frontend/components/layout/loading';
|
||||
import {useFetch} from "@gitroom/helpers/utils/custom.fetch";
|
||||
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
||||
|
||||
export const BillingComponent = () => {
|
||||
const fetch = useFetch();
|
||||
|
||||
const load = useCallback(async (path: string) => {
|
||||
return await (await fetch(path)).json();
|
||||
}, []);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import { Slider } from '@gitroom/react/form/slider';
|
||||
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Button } from '@gitroom/react/form/button';
|
||||
import { sortBy } from 'lodash';
|
||||
import { isEqual, sortBy } from 'lodash';
|
||||
import { Track } from '@gitroom/react/form/track';
|
||||
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
||||
import { Subscription } from '@prisma/client';
|
||||
|
|
@ -14,8 +14,10 @@ import { useToaster } from '@gitroom/react/toaster/toaster';
|
|||
import dayjs from 'dayjs';
|
||||
import clsx from 'clsx';
|
||||
import { pricing } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/pricing';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { FAQComponent } from '@gitroom/frontend/components/billing/faq.component';
|
||||
import { useSWRConfig } from 'swr';
|
||||
import { useUser } from '@gitroom/frontend/components/layout/user.context';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
export interface Tiers {
|
||||
month: Array<{
|
||||
|
|
@ -153,13 +155,15 @@ export const NoBillingComponent: FC<{
|
|||
sub?: Subscription;
|
||||
}> = (props) => {
|
||||
const { tiers, sub } = props;
|
||||
const { mutate } = useSWRConfig();
|
||||
const fetch = useFetch();
|
||||
const router = useRouter();
|
||||
const toast = useToaster();
|
||||
const user = useUser();
|
||||
|
||||
const [subscription, setSubscription] = useState<Subscription | undefined>(
|
||||
sub
|
||||
);
|
||||
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [period, setPeriod] = useState<'MONTHLY' | 'YEARLY'>(
|
||||
subscription?.period || 'MONTHLY'
|
||||
|
|
@ -169,10 +173,24 @@ export const NoBillingComponent: FC<{
|
|||
);
|
||||
|
||||
const [initialChannels, setInitialChannels] = useState(
|
||||
subscription?.totalChannels || 1
|
||||
sub?.totalChannels || 1
|
||||
);
|
||||
const [totalChannels, setTotalChannels] = useState<number>(initialChannels);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialChannels !== sub?.totalChannels) {
|
||||
setTotalChannels(sub?.totalChannels || 1);
|
||||
setInitialChannels(sub?.totalChannels || 1);
|
||||
}
|
||||
|
||||
if (period !== sub?.period) {
|
||||
setPeriod(sub?.period || 'MONTHLY');
|
||||
setMonthlyOrYearly(sub?.period === 'MONTHLY' ? 'off' : 'on');
|
||||
}
|
||||
|
||||
setSubscription(sub);
|
||||
}, [sub]);
|
||||
|
||||
const currentPackage = useMemo(() => {
|
||||
if (!subscription) {
|
||||
return 'FREE';
|
||||
|
|
@ -265,7 +283,17 @@ export const NoBillingComponent: FC<{
|
|||
subscriptionTier: billing,
|
||||
cancelAt: null,
|
||||
}));
|
||||
router.refresh();
|
||||
mutate(
|
||||
'/user/self',
|
||||
{
|
||||
...user,
|
||||
totalChannels,
|
||||
tier: billing,
|
||||
},
|
||||
{
|
||||
revalidate: false,
|
||||
}
|
||||
);
|
||||
toast.show('Subscription updated successfully');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import { AddProviderButton } from '@gitroom/frontend/components/launches/add.provider.component';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import Image from 'next/image';
|
||||
import { orderBy } from 'lodash';
|
||||
import { Calendar } from '@gitroom/frontend/components/launches/calendar';
|
||||
|
|
@ -10,22 +10,53 @@ import { Filters } from '@gitroom/frontend/components/launches/filters';
|
|||
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
||||
import useSWR from 'swr';
|
||||
import { LoadingComponent } from '@gitroom/frontend/components/layout/loading';
|
||||
import clsx from 'clsx';
|
||||
import { useUser } from '../layout/user.context';
|
||||
import { Menu } from '@gitroom/frontend/components/launches/menu/menu';
|
||||
|
||||
export const LaunchesComponent = () => {
|
||||
const fetch = useFetch();
|
||||
const [reload, setReload] = useState(false);
|
||||
const load = useCallback(async (path: string) => {
|
||||
return (await (await fetch(path)).json()).integrations;
|
||||
}, []);
|
||||
const user = useUser();
|
||||
|
||||
const { isLoading, data: integrations } = useSWR('/integrations/list', load, {
|
||||
const {
|
||||
isLoading,
|
||||
data: integrations,
|
||||
mutate,
|
||||
} = useSWR('/integrations/list', load, {
|
||||
fallbackData: [],
|
||||
});
|
||||
|
||||
const sortedIntegrations = useMemo(() => {
|
||||
return orderBy(integrations, ['type', 'identifier'], ['desc', 'asc']);
|
||||
const totalNonDisabledChannels = useMemo(() => {
|
||||
return (
|
||||
integrations?.filter((integration: any) => !integration.disabled)
|
||||
?.length || 0
|
||||
);
|
||||
}, [integrations]);
|
||||
|
||||
if (isLoading) {
|
||||
const sortedIntegrations = useMemo(() => {
|
||||
return orderBy(
|
||||
integrations,
|
||||
['type', 'disabled', 'identifier'],
|
||||
['desc', 'asc', 'asc']
|
||||
);
|
||||
}, [integrations]);
|
||||
|
||||
const update = useCallback(async (shouldReload: boolean) => {
|
||||
if (shouldReload) {
|
||||
setReload(true);
|
||||
}
|
||||
await mutate();
|
||||
|
||||
if (shouldReload) {
|
||||
setReload(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (isLoading || reload) {
|
||||
return <LoadingComponent />;
|
||||
}
|
||||
|
||||
|
|
@ -45,7 +76,12 @@ export const LaunchesComponent = () => {
|
|||
key={integration.id}
|
||||
className="flex gap-[8px] items-center"
|
||||
>
|
||||
<div className="relative w-[34px] h-[34px] rounded-full flex justify-center items-center bg-fifth">
|
||||
<div
|
||||
className={clsx(
|
||||
'relative w-[34px] h-[34px] rounded-full flex justify-center items-center bg-fifth',
|
||||
integration.disabled && 'opacity-50'
|
||||
)}
|
||||
>
|
||||
<img
|
||||
src={integration.picture}
|
||||
className="rounded-full"
|
||||
|
|
@ -61,7 +97,31 @@ export const LaunchesComponent = () => {
|
|||
height={20}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1">{integration.name}</div>
|
||||
<div
|
||||
{...(integration.disabled &&
|
||||
totalNonDisabledChannels === user?.totalChannels
|
||||
? {
|
||||
'data-tooltip-id': 'tooltip',
|
||||
'data-tooltip-content':
|
||||
'This channel is disabled, please upgrade your plan to enable it.',
|
||||
}
|
||||
: {})}
|
||||
className={clsx(
|
||||
'flex-1',
|
||||
integration.disabled && 'opacity-50'
|
||||
)}
|
||||
>
|
||||
{integration.name}
|
||||
</div>
|
||||
<Menu
|
||||
onChange={update}
|
||||
id={integration.id}
|
||||
canEnable={
|
||||
user?.totalChannels! > totalNonDisabledChannels &&
|
||||
integration.disabled
|
||||
}
|
||||
canDisable={!integration.disabled}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,172 @@
|
|||
import { FC, useCallback, useState } from 'react';
|
||||
import { useClickOutside } from '@mantine/hooks';
|
||||
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
||||
import { deleteDialog } from '@gitroom/react/helpers/delete.dialog';
|
||||
import { useToaster } from '@gitroom/react/toaster/toaster';
|
||||
|
||||
export const Menu: FC<{
|
||||
canEnable: boolean;
|
||||
canDisable: boolean;
|
||||
id: string;
|
||||
onChange: (shouldReload: boolean) => void;
|
||||
}> = (props) => {
|
||||
const { canEnable, canDisable, id, onChange } = props;
|
||||
const fetch = useFetch();
|
||||
const toast = useToaster();
|
||||
const [show, setShow] = useState(false);
|
||||
const ref = useClickOutside<HTMLDivElement>(() => {
|
||||
setShow(false);
|
||||
});
|
||||
|
||||
const changeShow = useCallback(() => {
|
||||
setShow(!show);
|
||||
}, [show]);
|
||||
|
||||
const disableChannel = useCallback(async () => {
|
||||
if (
|
||||
!(await deleteDialog(
|
||||
'Are you sure you want to disable this channel?',
|
||||
'Disable Channel'
|
||||
))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
await fetch('/integrations/disable', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ id }),
|
||||
});
|
||||
|
||||
toast.show('Channel Disabled', 'success');
|
||||
setShow(false);
|
||||
onChange(false);
|
||||
}, []);
|
||||
|
||||
const deleteChannel = useCallback(async () => {
|
||||
if (
|
||||
!(await deleteDialog(
|
||||
'Are you sure you want to delete this channel?',
|
||||
'Delete Channel'
|
||||
))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const deleteIntegration = await fetch('/integrations', {
|
||||
method: 'DELETE',
|
||||
body: JSON.stringify({ id }),
|
||||
});
|
||||
|
||||
if (deleteIntegration.status === 406) {
|
||||
toast.show(
|
||||
'You have to delete all the posts associated with this channel before deleting it',
|
||||
'warning'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
toast.show('Channel Deleted', 'success');
|
||||
setShow(false);
|
||||
onChange(true);
|
||||
}, []);
|
||||
|
||||
const enableChannel = useCallback(async () => {
|
||||
await fetch('/integrations/enable', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ id }),
|
||||
});
|
||||
|
||||
toast.show('Channel Enabled', 'success');
|
||||
setShow(false);
|
||||
onChange(false);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="cursor-pointer relative select-none"
|
||||
onClick={changeShow}
|
||||
ref={ref}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M13.125 12C13.125 12.2225 13.059 12.44 12.9354 12.625C12.8118 12.81 12.6361 12.9542 12.4305 13.0394C12.225 13.1245 11.9988 13.1468 11.7805 13.1034C11.5623 13.06 11.3618 12.9528 11.2045 12.7955C11.0472 12.6382 10.94 12.4377 10.8966 12.2195C10.8532 12.0012 10.8755 11.775 10.9606 11.5695C11.0458 11.3639 11.19 11.1882 11.375 11.0646C11.56 10.941 11.7775 10.875 12 10.875C12.2984 10.875 12.5845 10.9935 12.7955 11.2045C13.0065 11.4155 13.125 11.7016 13.125 12ZM12 6.75C12.2225 6.75 12.44 6.68402 12.625 6.5604C12.81 6.43679 12.9542 6.26109 13.0394 6.05552C13.1245 5.84995 13.1468 5.62375 13.1034 5.40552C13.06 5.1873 12.9528 4.98684 12.7955 4.82951C12.6382 4.67217 12.4377 4.56503 12.2195 4.52162C12.0012 4.47821 11.775 4.50049 11.5695 4.58564C11.3639 4.67078 11.1882 4.81498 11.0646 4.99998C10.941 5.18499 10.875 5.4025 10.875 5.625C10.875 5.92337 10.9935 6.20952 11.2045 6.4205C11.4155 6.63147 11.7016 6.75 12 6.75ZM12 17.25C11.7775 17.25 11.56 17.316 11.375 17.4396C11.19 17.5632 11.0458 17.7389 10.9606 17.9445C10.8755 18.15 10.8532 18.3762 10.8966 18.5945C10.94 18.8127 11.0472 19.0132 11.2045 19.1705C11.3618 19.3278 11.5623 19.435 11.7805 19.4784C11.9988 19.5218 12.225 19.4995 12.4305 19.4144C12.6361 19.3292 12.8118 19.185 12.9354 19C13.059 18.815 13.125 18.5975 13.125 18.375C13.125 18.0766 13.0065 17.7905 12.7955 17.5795C12.5845 17.3685 12.2984 17.25 12 17.25Z"
|
||||
fill="#506490"
|
||||
/>
|
||||
</svg>
|
||||
{show && (
|
||||
<div
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="absolute top-[100%] left-0 p-[8px] px-[20px] bg-fifth flex flex-col gap-[16px] z-[100] rounded-[8px] border border-tableBorder font-['Inter'] text-nowrap"
|
||||
>
|
||||
{canEnable && (
|
||||
<div
|
||||
className="flex gap-[12px] items-center"
|
||||
onClick={enableChannel}
|
||||
>
|
||||
<div>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M28.2325 12.8525C27.7612 12.36 27.2738 11.8525 27.09 11.4062C26.92 10.9975 26.91 10.32 26.9 9.66375C26.8813 8.44375 26.8612 7.06125 25.9 6.1C24.9387 5.13875 23.5562 5.11875 22.3363 5.1C21.68 5.09 21.0025 5.08 20.5938 4.91C20.1488 4.72625 19.64 4.23875 19.1475 3.7675C18.285 2.93875 17.305 2 16 2C14.695 2 13.7162 2.93875 12.8525 3.7675C12.36 4.23875 11.8525 4.72625 11.4062 4.91C11 5.08 10.32 5.09 9.66375 5.1C8.44375 5.11875 7.06125 5.13875 6.1 6.1C5.13875 7.06125 5.125 8.44375 5.1 9.66375C5.09 10.32 5.08 10.9975 4.91 11.4062C4.72625 11.8512 4.23875 12.36 3.7675 12.8525C2.93875 13.715 2 14.695 2 16C2 17.305 2.93875 18.2837 3.7675 19.1475C4.23875 19.64 4.72625 20.1475 4.91 20.5938C5.08 21.0025 5.09 21.68 5.1 22.3363C5.11875 23.5562 5.13875 24.9387 6.1 25.9C7.06125 26.8612 8.44375 26.8813 9.66375 26.9C10.32 26.91 10.9975 26.92 11.4062 27.09C11.8512 27.2738 12.36 27.7612 12.8525 28.2325C13.715 29.0613 14.695 30 16 30C17.305 30 18.2837 29.0613 19.1475 28.2325C19.64 27.7612 20.1475 27.2738 20.5938 27.09C21.0025 26.92 21.68 26.91 22.3363 26.9C23.5562 26.8813 24.9387 26.8612 25.9 25.9C26.8612 24.9387 26.8813 23.5562 26.9 22.3363C26.91 21.68 26.92 21.0025 27.09 20.5938C27.2738 20.1488 27.7612 19.64 28.2325 19.1475C29.0613 18.285 30 17.305 30 16C30 14.695 29.0613 13.7162 28.2325 12.8525ZM26.7887 17.7638C26.19 18.3888 25.57 19.035 25.2412 19.8288C24.9262 20.5913 24.9125 21.4625 24.9 22.3062C24.8875 23.1812 24.8738 24.0975 24.485 24.485C24.0963 24.8725 23.1862 24.8875 22.3062 24.9C21.4625 24.9125 20.5913 24.9262 19.8288 25.2412C19.035 25.57 18.3888 26.19 17.7638 26.7887C17.1388 27.3875 16.5 28 16 28C15.5 28 14.8562 27.385 14.2362 26.7887C13.6163 26.1925 12.965 25.57 12.1713 25.2412C11.4088 24.9262 10.5375 24.9125 9.69375 24.9C8.81875 24.8875 7.9025 24.8738 7.515 24.485C7.1275 24.0963 7.1125 23.1862 7.1 22.3062C7.0875 21.4625 7.07375 20.5913 6.75875 19.8288C6.43 19.035 5.81 18.3888 5.21125 17.7638C4.6125 17.1388 4 16.5 4 16C4 15.5 4.615 14.8562 5.21125 14.2362C5.8075 13.6163 6.43 12.965 6.75875 12.1713C7.07375 11.4088 7.0875 10.5375 7.1 9.69375C7.1125 8.81875 7.12625 7.9025 7.515 7.515C7.90375 7.1275 8.81375 7.1125 9.69375 7.1C10.5375 7.0875 11.4088 7.07375 12.1713 6.75875C12.965 6.43 13.6112 5.81 14.2362 5.21125C14.8612 4.6125 15.5 4 16 4C16.5 4 17.1438 4.615 17.7638 5.21125C18.3838 5.8075 19.035 6.43 19.8288 6.75875C20.5913 7.07375 21.4625 7.0875 22.3062 7.1C23.1812 7.1125 24.0975 7.12625 24.485 7.515C24.8725 7.90375 24.8875 8.81375 24.9 9.69375C24.9125 10.5375 24.9262 11.4088 25.2412 12.1713C25.57 12.965 26.19 13.6112 26.7887 14.2362C27.3875 14.8612 28 15.5 28 16C28 16.5 27.385 17.1438 26.7887 17.7638ZM21.7075 12.2925C21.8005 12.3854 21.8742 12.4957 21.9246 12.6171C21.9749 12.7385 22.0008 12.8686 22.0008 13C22.0008 13.1314 21.9749 13.2615 21.9246 13.3829C21.8742 13.5043 21.8005 13.6146 21.7075 13.7075L14.7075 20.7075C14.6146 20.8005 14.5043 20.8742 14.3829 20.9246C14.2615 20.9749 14.1314 21.0008 14 21.0008C13.8686 21.0008 13.7385 20.9749 13.6171 20.9246C13.4957 20.8742 13.3854 20.8005 13.2925 20.7075L10.2925 17.7075C10.1049 17.5199 9.99944 17.2654 9.99944 17C9.99944 16.7346 10.1049 16.4801 10.2925 16.2925C10.4801 16.1049 10.7346 15.9994 11 15.9994C11.2654 15.9994 11.5199 16.1049 11.7075 16.2925L14 18.5863L20.2925 12.2925C20.3854 12.1995 20.4957 12.1258 20.6171 12.0754C20.7385 12.0251 20.8686 11.9992 21 11.9992C21.1314 11.9992 21.2615 12.0251 21.3829 12.0754C21.5043 12.1258 21.6146 12.1995 21.7075 12.2925Z"
|
||||
fill="#06ff00"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="text-[12px]">Enable Channel</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{canDisable && (
|
||||
<div
|
||||
className="flex gap-[12px] items-center"
|
||||
onClick={disableChannel}
|
||||
>
|
||||
<div>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M16 3C13.4288 3 10.9154 3.76244 8.77759 5.1909C6.63975 6.61935 4.97351 8.64968 3.98957 11.0251C3.00563 13.4006 2.74819 16.0144 3.2498 18.5362C3.75141 21.0579 4.98953 23.3743 6.80762 25.1924C8.6257 27.0105 10.9421 28.2486 13.4638 28.7502C15.9856 29.2518 18.5995 28.9944 20.9749 28.0104C23.3503 27.0265 25.3807 25.3603 26.8091 23.2224C28.2376 21.0846 29 18.5712 29 16C28.9964 12.5533 27.6256 9.24882 25.1884 6.81163C22.7512 4.37445 19.4467 3.00364 16 3ZM27 16C27.0026 18.5719 26.0993 21.0626 24.4488 23.035L8.96501 7.55C10.5713 6.21372 12.5249 5.36255 14.5972 5.0961C16.6696 4.82964 18.775 5.15892 20.667 6.04541C22.5591 6.93189 24.1595 8.33891 25.281 10.1018C26.4026 11.8647 26.9988 13.9106 27 16ZM5.00001 16C4.99745 13.4281 5.90069 10.9374 7.55126 8.965L23.035 24.45C21.4288 25.7863 19.4751 26.6374 17.4028 26.9039C15.3304 27.1704 13.225 26.8411 11.333 25.9546C9.44096 25.0681 7.84053 23.6611 6.71899 21.8982C5.59745 20.1353 5.0012 18.0894 5.00001 16Z"
|
||||
fill="#F97066"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="text-[12px]">Disable Channel</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-[12px] items-center" onClick={deleteChannel}>
|
||||
<div>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M13.5 3H11V2.5C11 2.10218 10.842 1.72064 10.5607 1.43934C10.2794 1.15804 9.89782 1 9.5 1H6.5C6.10218 1 5.72064 1.15804 5.43934 1.43934C5.15804 1.72064 5 2.10218 5 2.5V3H2.5C2.36739 3 2.24021 3.05268 2.14645 3.14645C2.05268 3.24021 2 3.36739 2 3.5C2 3.63261 2.05268 3.75979 2.14645 3.85355C2.24021 3.94732 2.36739 4 2.5 4H3V13C3 13.2652 3.10536 13.5196 3.29289 13.7071C3.48043 13.8946 3.73478 14 4 14H12C12.2652 14 12.5196 13.8946 12.7071 13.7071C12.8946 13.5196 13 13.2652 13 13V4H13.5C13.6326 4 13.7598 3.94732 13.8536 3.85355C13.9473 3.75979 14 3.63261 14 3.5C14 3.36739 13.9473 3.24021 13.8536 3.14645C13.7598 3.05268 13.6326 3 13.5 3ZM6 2.5C6 2.36739 6.05268 2.24021 6.14645 2.14645C6.24021 2.05268 6.36739 2 6.5 2H9.5C9.63261 2 9.75979 2.05268 9.85355 2.14645C9.94732 2.24021 10 2.36739 10 2.5V3H6V2.5ZM12 13H4V4H12V13ZM7 6.5V10.5C7 10.6326 6.94732 10.7598 6.85355 10.8536C6.75979 10.9473 6.63261 11 6.5 11C6.36739 11 6.24021 10.9473 6.14645 10.8536C6.05268 10.7598 6 10.6326 6 10.5V6.5C6 6.36739 6.05268 6.24021 6.14645 6.14645C6.24021 6.05268 6.36739 6 6.5 6C6.63261 6 6.75979 6.05268 6.85355 6.14645C6.94732 6.24021 7 6.36739 7 6.5ZM10 6.5V10.5C10 10.6326 9.94732 10.7598 9.85355 10.8536C9.75979 10.9473 9.63261 11 9.5 11C9.36739 11 9.24021 10.9473 9.14645 10.8536C9.05268 10.7598 9 10.6326 9 10.5V6.5C9 6.36739 9.05268 6.24021 9.14645 6.14645C9.24021 6.05268 9.36739 6 9.5 6C9.63261 6 9.75979 6.05268 9.85355 6.14645C9.94732 6.24021 10 6.36739 10 6.5Z"
|
||||
fill="#F97066"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="text-[12px]">Delete</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -33,7 +33,13 @@ export const LayoutSettings = ({ children }: { children: ReactNode }) => {
|
|||
return await (await fetch(path)).json();
|
||||
}, []);
|
||||
|
||||
const { data: user } = useSWR('/user/self', load);
|
||||
const { data: user } = useSWR('/user/self', load, {
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnReconnect: false,
|
||||
revalidateIfStale: false,
|
||||
refreshWhenOffline: false,
|
||||
refreshWhenHidden: false,
|
||||
});
|
||||
|
||||
return (
|
||||
<ContextWrapper user={user}>
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ export const TopMenu: FC = () => {
|
|||
const user = useUser();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="flex flex-col h-full animate-normalFadeDown">
|
||||
<ul className="gap-5 flex flex-1 items-center text-[18px]">
|
||||
{menuItems
|
||||
.filter((f) => {
|
||||
|
|
@ -48,7 +48,7 @@ export const TopMenu: FC = () => {
|
|||
.map((item, index) => (
|
||||
<li key={item.name}>
|
||||
<Link
|
||||
prefetch={true}
|
||||
prefetch={false}
|
||||
href={item.path}
|
||||
className={clsx(
|
||||
'flex gap-2 items-center box',
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export const UserContext = createContext<
|
|||
orgId: string;
|
||||
tier: PricingInnerInterface;
|
||||
role: 'USER' | 'ADMIN' | 'SUPERADMIN';
|
||||
totalChannels: number;
|
||||
})
|
||||
>(undefined);
|
||||
|
||||
|
|
@ -21,15 +22,12 @@ export const ContextWrapper: FC<{
|
|||
orgId: string;
|
||||
tier: 'FREE' | 'STANDARD' | 'PRO';
|
||||
role: 'USER' | 'ADMIN' | 'SUPERADMIN';
|
||||
totalChannels: number;
|
||||
};
|
||||
children: ReactNode;
|
||||
}> = ({ user, children }) => {
|
||||
const values = user ? { ...user, tier: pricing[user.tier] } : ({} as any);
|
||||
return (
|
||||
<UserContext.Provider value={values}>
|
||||
{children}
|
||||
</UserContext.Provider>
|
||||
);
|
||||
return <UserContext.Provider value={values}>{children}</UserContext.Provider>;
|
||||
};
|
||||
|
||||
export const useUser = () => useContext(UserContext);
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ export const SettingsComponent = () => {
|
|||
|
||||
const fetch = useFetch();
|
||||
|
||||
console.log(user);
|
||||
|
||||
const load = useCallback(async (path: string) => {
|
||||
const { github } = await (await fetch('/settings/github')).json();
|
||||
if (!github) {
|
||||
|
|
|
|||
|
|
@ -48,7 +48,8 @@ module.exports = {
|
|||
},
|
||||
boxShadow: {
|
||||
yellow: '0 0 60px 20px #6b6237',
|
||||
green: '0px 0px 50px rgba(60, 124, 90, 0.3)',
|
||||
yellowToast: '0px 0px 50px rgba(252, 186, 3, 0.3)',
|
||||
greenToast: '0px 0px 50px rgba(60, 124, 90, 0.3)',
|
||||
},
|
||||
// that is actual animation
|
||||
keyframes: (theme) => ({
|
||||
|
|
|
|||
|
|
@ -4,7 +4,10 @@ import dayjs from 'dayjs';
|
|||
|
||||
@Injectable()
|
||||
export class IntegrationRepository {
|
||||
constructor(private _integration: PrismaRepository<'integration'>) {}
|
||||
constructor(
|
||||
private _integration: PrismaRepository<'integration'>,
|
||||
private _posts: PrismaRepository<'post'>
|
||||
) {}
|
||||
|
||||
createOrUpdateIntegration(
|
||||
org: string,
|
||||
|
|
@ -77,7 +80,82 @@ export class IntegrationRepository {
|
|||
return this._integration.model.integration.findMany({
|
||||
where: {
|
||||
organizationId: org,
|
||||
deletedAt: null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async disableChannel(org: string, id: string) {
|
||||
await this._integration.model.integration.update({
|
||||
where: {
|
||||
id,
|
||||
organizationId: org,
|
||||
},
|
||||
data: {
|
||||
disabled: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async enableChannel(org: string, id: string) {
|
||||
await this._integration.model.integration.update({
|
||||
where: {
|
||||
id,
|
||||
organizationId: org,
|
||||
},
|
||||
data: {
|
||||
disabled: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
countPostsForChannel(org: string, id: string) {
|
||||
return this._posts.model.post.count({
|
||||
where: {
|
||||
organizationId: org,
|
||||
integrationId: id,
|
||||
deletedAt: null,
|
||||
state: {
|
||||
in: ['QUEUE', 'DRAFT'],
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
deleteChannel(org: string, id: string) {
|
||||
return this._integration.model.integration.update({
|
||||
where: {
|
||||
id,
|
||||
organizationId: org,
|
||||
},
|
||||
data: {
|
||||
deletedAt: new Date(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async disableIntegrations(org: string, totalChannels: number) {
|
||||
const getChannels = await this._integration.model.integration.findMany({
|
||||
where: {
|
||||
organizationId: org,
|
||||
disabled: false,
|
||||
deletedAt: null,
|
||||
},
|
||||
take: totalChannels,
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
for (const channel of getChannels) {
|
||||
await this._integration.model.integration.update({
|
||||
where: {
|
||||
id: channel.id,
|
||||
},
|
||||
data: {
|
||||
disabled: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import {HttpException, HttpStatus, Injectable} from '@nestjs/common';
|
||||
import { IntegrationRepository } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.repository';
|
||||
import { IntegrationManager } from '@gitroom/nestjs-libraries/integrations/integration.manager';
|
||||
|
||||
|
|
@ -63,4 +63,32 @@ export class IntegrationService {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
async disableChannel(org: string, id: string) {
|
||||
return this._integrationRepository.disableChannel(org, id);
|
||||
}
|
||||
|
||||
async enableChannel(org: string, totalChannels: number, id: string) {
|
||||
const integrations = (
|
||||
await this._integrationRepository.getIntegrationsList(org)
|
||||
).filter((f) => !f.disabled);
|
||||
if (integrations.length >= totalChannels) {
|
||||
throw new Error('You have reached the maximum number of channels');
|
||||
}
|
||||
|
||||
return this._integrationRepository.enableChannel(org, id);
|
||||
}
|
||||
|
||||
async deleteChannel(org: string, id: string) {
|
||||
const isTherePosts = await this._integrationRepository.countPostsForChannel(org, id);
|
||||
if (isTherePosts) {
|
||||
throw new HttpException('There are posts for this channel', HttpStatus.NOT_ACCEPTABLE);
|
||||
}
|
||||
|
||||
return this._integrationRepository.deleteChannel(org, id);
|
||||
}
|
||||
|
||||
async disableIntegrations(org: string, totalChannels: number) {
|
||||
return this._integrationRepository.disableIntegrations(org, totalChannels);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ export class OrganizationRepository {
|
|||
subscription: {
|
||||
select: {
|
||||
subscriptionTier: true,
|
||||
totalChannels: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -186,4 +187,18 @@ export class OrganizationRepository {
|
|||
},
|
||||
});
|
||||
}
|
||||
|
||||
disableOrEnableNonSuperAdminUsers(orgId: string, disable: boolean) {
|
||||
return this._userOrg.model.userOrganization.updateMany({
|
||||
where: {
|
||||
organizationId: orgId,
|
||||
role: {
|
||||
not: Role.SUPERADMIN,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
disabled: disable,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,4 +76,8 @@ export class OrganizationService {
|
|||
|
||||
return this._organizationRepository.deleteTeamMember(org.id, userId);
|
||||
}
|
||||
|
||||
disableOrEnableNonSuperAdminUsers(orgId: string, disable: boolean) {
|
||||
return this._organizationRepository.disableOrEnableNonSuperAdminUsers(orgId, disable);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ model UserOrganization {
|
|||
userId String
|
||||
organization Organization @relation(fields: [organizationId], references: [id])
|
||||
organizationId String
|
||||
disabled Boolean @default(false)
|
||||
role Role @default(USER)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
|
@ -144,6 +145,7 @@ model Integration {
|
|||
providerIdentifier String
|
||||
type String
|
||||
token String
|
||||
disabled Boolean @default(false)
|
||||
tokenExpiration DateTime?
|
||||
refreshToken String?
|
||||
posts Post[]
|
||||
|
|
|
|||
|
|
@ -1,34 +1,86 @@
|
|||
import {Injectable} from "@nestjs/common";
|
||||
import {pricing} from "@gitroom/nestjs-libraries/database/prisma/subscriptions/pricing";
|
||||
import {SubscriptionRepository} from "@gitroom/nestjs-libraries/database/prisma/subscriptions/subscription.repository";
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { pricing } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/pricing';
|
||||
import { SubscriptionRepository } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/subscription.repository';
|
||||
import { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';
|
||||
import { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.service';
|
||||
|
||||
@Injectable()
|
||||
export class SubscriptionService {
|
||||
constructor(
|
||||
private readonly _subscriptionRepository: SubscriptionRepository
|
||||
private readonly _subscriptionRepository: SubscriptionRepository,
|
||||
private readonly _integrationService: IntegrationService,
|
||||
private readonly _organizationService: OrganizationService
|
||||
) {}
|
||||
|
||||
getSubscriptionByOrganizationId(organizationId: string) {
|
||||
return this._subscriptionRepository.getSubscriptionByOrganizationId(organizationId);
|
||||
return this._subscriptionRepository.getSubscriptionByOrganizationId(
|
||||
organizationId
|
||||
);
|
||||
}
|
||||
|
||||
async deleteSubscription(customerId: string) {
|
||||
await this.modifySubscription(customerId, 'FREE');
|
||||
return this._subscriptionRepository.deleteSubscriptionByCustomerId(customerId);
|
||||
await this.modifySubscription(
|
||||
customerId,
|
||||
pricing.FREE.channel || 0,
|
||||
'FREE'
|
||||
);
|
||||
return this._subscriptionRepository.deleteSubscriptionByCustomerId(
|
||||
customerId
|
||||
);
|
||||
}
|
||||
|
||||
updateCustomerId(organizationId: string, customerId: string) {
|
||||
return this._subscriptionRepository.updateCustomerId(organizationId, customerId);
|
||||
return this._subscriptionRepository.updateCustomerId(
|
||||
organizationId,
|
||||
customerId
|
||||
);
|
||||
}
|
||||
|
||||
checkSubscription(organizationId: string, subscriptionId: string) {
|
||||
return this._subscriptionRepository.checkSubscription(organizationId, subscriptionId);
|
||||
return this._subscriptionRepository.checkSubscription(
|
||||
organizationId,
|
||||
subscriptionId
|
||||
);
|
||||
}
|
||||
|
||||
async modifySubscription(customerId: string, billing: 'FREE' | 'STANDARD' | 'PRO') {
|
||||
const getCurrentSubscription = (await this._subscriptionRepository.getSubscriptionByCustomerId(customerId))!;
|
||||
async modifySubscription(
|
||||
customerId: string,
|
||||
totalChannels: number,
|
||||
billing: 'FREE' | 'STANDARD' | 'PRO'
|
||||
) {
|
||||
const getCurrentSubscription =
|
||||
(await this._subscriptionRepository.getSubscriptionByCustomerId(
|
||||
customerId
|
||||
))!;
|
||||
const from = pricing[getCurrentSubscription?.subscriptionTier || 'FREE'];
|
||||
const to = pricing[billing];
|
||||
const to = pricing[billing];
|
||||
|
||||
const currentTotalChannels = (
|
||||
await this._integrationService.getIntegrationsList(
|
||||
getCurrentSubscription.organizationId
|
||||
)
|
||||
).filter((f) => !f.disabled);
|
||||
|
||||
if (currentTotalChannels.length > totalChannels) {
|
||||
await this._integrationService.disableIntegrations(
|
||||
getCurrentSubscription.organizationId,
|
||||
currentTotalChannels.length - totalChannels
|
||||
);
|
||||
}
|
||||
|
||||
if (from.team_members && !to.team_members) {
|
||||
await this._organizationService.disableOrEnableNonSuperAdminUsers(
|
||||
getCurrentSubscription.organizationId,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
if (!from.team_members && to.team_members) {
|
||||
await this._organizationService.disableOrEnableNonSuperAdminUsers(
|
||||
getCurrentSubscription.organizationId,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
// if (to.faq < from.faq) {
|
||||
// await this._faqRepository.deleteFAQs(getCurrentSubscription?.organizationId, from.faq - to.faq);
|
||||
|
|
@ -48,9 +100,23 @@ export class SubscriptionService {
|
|||
// }
|
||||
}
|
||||
|
||||
async createOrUpdateSubscription(identifier: string, customerId: string, totalChannels: number, billing: 'STANDARD' | 'PRO', period: 'MONTHLY' | 'YEARLY', cancelAt: number | null) {
|
||||
await this.modifySubscription(customerId, billing);
|
||||
return this._subscriptionRepository.createOrUpdateSubscription(identifier, customerId, totalChannels, billing, period, cancelAt);
|
||||
async createOrUpdateSubscription(
|
||||
identifier: string,
|
||||
customerId: string,
|
||||
totalChannels: number,
|
||||
billing: 'STANDARD' | 'PRO',
|
||||
period: 'MONTHLY' | 'YEARLY',
|
||||
cancelAt: number | null
|
||||
) {
|
||||
await this.modifySubscription(customerId, totalChannels, billing);
|
||||
return this._subscriptionRepository.createOrUpdateSubscription(
|
||||
identifier,
|
||||
customerId,
|
||||
totalChannels,
|
||||
billing,
|
||||
period,
|
||||
cancelAt
|
||||
);
|
||||
}
|
||||
|
||||
async getSubscription(organizationId: string) {
|
||||
|
|
|
|||
|
|
@ -1,19 +1,28 @@
|
|||
'use client';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import EventEmitter from 'events';
|
||||
import clsx from 'clsx';
|
||||
|
||||
const toaster = new EventEmitter();
|
||||
export const Toaster = () => {
|
||||
const [showToaster, setShowToaster] = useState(false);
|
||||
const [toasterText, setToasterText] = useState('');
|
||||
const [toasterType, setToasterType] = useState<'success' | 'warning' | ''>(
|
||||
''
|
||||
);
|
||||
useEffect(() => {
|
||||
toaster.on('show', (text: string) => {
|
||||
setToasterText(text);
|
||||
setShowToaster(true);
|
||||
setTimeout(() => {
|
||||
setShowToaster(false);
|
||||
}, 4200);
|
||||
});
|
||||
toaster.on(
|
||||
'show',
|
||||
(params: { text: string; type?: 'success' | 'warning' }) => {
|
||||
const { text, type } = params;
|
||||
setToasterText(text);
|
||||
setToasterType(type || 'success');
|
||||
setShowToaster(true);
|
||||
setTimeout(() => {
|
||||
setShowToaster(false);
|
||||
}, 4200);
|
||||
}
|
||||
);
|
||||
return () => {
|
||||
toaster.removeAllListeners();
|
||||
};
|
||||
|
|
@ -24,20 +33,40 @@ export const Toaster = () => {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="animate-fadeDown shadow-green rounded-[8px] gap-[18px] flex items-center overflow-hidden bg-[#0F1524] p-[16px] min-w-[319px] fixed left-[50%] text-white z-[300] top-[32px] -translate-x-[50%] h-[56px]">
|
||||
<div
|
||||
className={clsx(
|
||||
'animate-fadeDown rounded-[8px] gap-[18px] flex items-center overflow-hidden bg-[#0F1524] p-[16px] min-w-[319px] fixed left-[50%] text-white z-[300] top-[32px] -translate-x-[50%] h-[56px]',
|
||||
toasterType === 'success' ? 'shadow-greenToast' : 'shadow-yellowToast'
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M12 2.25C10.0716 2.25 8.18657 2.82183 6.58319 3.89317C4.97982 4.96452 3.73013 6.48726 2.99218 8.26884C2.25422 10.0504 2.06114 12.0108 2.43735 13.9021C2.81355 15.7934 3.74215 17.5307 5.10571 18.8943C6.46928 20.2579 8.20656 21.1865 10.0979 21.5627C11.9892 21.9389 13.9496 21.7458 15.7312 21.0078C17.5127 20.2699 19.0355 19.0202 20.1068 17.4168C21.1782 15.8134 21.75 13.9284 21.75 12C21.7473 9.41498 20.7192 6.93661 18.8913 5.10872C17.0634 3.28084 14.585 2.25273 12 2.25ZM16.2806 10.2806L11.0306 15.5306C10.961 15.6004 10.8783 15.6557 10.7872 15.6934C10.6962 15.7312 10.5986 15.7506 10.5 15.7506C10.4014 15.7506 10.3038 15.7312 10.2128 15.6934C10.1218 15.6557 10.039 15.6004 9.96938 15.5306L7.71938 13.2806C7.57865 13.1399 7.49959 12.949 7.49959 12.75C7.49959 12.551 7.57865 12.3601 7.71938 12.2194C7.86011 12.0786 8.05098 11.9996 8.25 11.9996C8.44903 11.9996 8.6399 12.0786 8.78063 12.2194L10.5 13.9397L15.2194 9.21937C15.2891 9.14969 15.3718 9.09442 15.4628 9.0567C15.5539 9.01899 15.6515 8.99958 15.75 8.99958C15.8486 8.99958 15.9461 9.01899 16.0372 9.0567C16.1282 9.09442 16.2109 9.14969 16.2806 9.21937C16.3503 9.28906 16.4056 9.37178 16.4433 9.46283C16.481 9.55387 16.5004 9.65145 16.5004 9.75C16.5004 9.84855 16.481 9.94613 16.4433 10.0372C16.4056 10.1282 16.3503 10.2109 16.2806 10.2806Z"
|
||||
fill="#6CE9A6"
|
||||
/>
|
||||
</svg>
|
||||
{toasterType === 'success' ? (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M12 2.25C10.0716 2.25 8.18657 2.82183 6.58319 3.89317C4.97982 4.96452 3.73013 6.48726 2.99218 8.26884C2.25422 10.0504 2.06114 12.0108 2.43735 13.9021C2.81355 15.7934 3.74215 17.5307 5.10571 18.8943C6.46928 20.2579 8.20656 21.1865 10.0979 21.5627C11.9892 21.9389 13.9496 21.7458 15.7312 21.0078C17.5127 20.2699 19.0355 19.0202 20.1068 17.4168C21.1782 15.8134 21.75 13.9284 21.75 12C21.7473 9.41498 20.7192 6.93661 18.8913 5.10872C17.0634 3.28084 14.585 2.25273 12 2.25ZM16.2806 10.2806L11.0306 15.5306C10.961 15.6004 10.8783 15.6557 10.7872 15.6934C10.6962 15.7312 10.5986 15.7506 10.5 15.7506C10.4014 15.7506 10.3038 15.7312 10.2128 15.6934C10.1218 15.6557 10.039 15.6004 9.96938 15.5306L7.71938 13.2806C7.57865 13.1399 7.49959 12.949 7.49959 12.75C7.49959 12.551 7.57865 12.3601 7.71938 12.2194C7.86011 12.0786 8.05098 11.9996 8.25 11.9996C8.44903 11.9996 8.6399 12.0786 8.78063 12.2194L10.5 13.9397L15.2194 9.21937C15.2891 9.14969 15.3718 9.09442 15.4628 9.0567C15.5539 9.01899 15.6515 8.99958 15.75 8.99958C15.8486 8.99958 15.9461 9.01899 16.0372 9.0567C16.1282 9.09442 16.2109 9.14969 16.2806 9.21937C16.3503 9.28906 16.4056 9.37178 16.4433 9.46283C16.481 9.55387 16.5004 9.65145 16.5004 9.75C16.5004 9.84855 16.481 9.94613 16.4433 10.0372C16.4056 10.1282 16.3503 10.2109 16.2806 10.2806Z"
|
||||
fill="#6CE9A6"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M22.201 17.6334L14.0026 3.39556C13.7977 3.04674 13.5052 2.75752 13.1541 2.55656C12.803 2.3556 12.4055 2.24988 12.001 2.24988C11.5965 2.24988 11.199 2.3556 10.8479 2.55656C10.4968 2.75752 10.2043 3.04674 9.99944 3.39556L1.80101 17.6334C1.60388 17.9708 1.5 18.3545 1.5 18.7453C1.5 19.136 1.60388 19.5197 1.80101 19.8571C2.00325 20.2081 2.29523 20.4989 2.64697 20.6997C2.99871 20.9005 3.39755 21.0041 3.80257 20.9999H20.1994C20.6041 21.0038 21.0026 20.9 21.354 20.6992C21.7054 20.4984 21.997 20.2078 22.1991 19.8571C22.3965 19.5199 22.5007 19.1363 22.5011 18.7455C22.5014 18.3548 22.3978 17.9709 22.201 17.6334ZM11.251 9.74994C11.251 9.55103 11.33 9.36026 11.4707 9.21961C11.6113 9.07896 11.8021 8.99994 12.001 8.99994C12.1999 8.99994 12.3907 9.07896 12.5313 9.21961C12.672 9.36026 12.751 9.55103 12.751 9.74994V13.4999C12.751 13.6989 12.672 13.8896 12.5313 14.0303C12.3907 14.1709 12.1999 14.2499 12.001 14.2499C11.8021 14.2499 11.6113 14.1709 11.4707 14.0303C11.33 13.8896 11.251 13.6989 11.251 13.4999V9.74994ZM12.001 17.9999C11.7785 17.9999 11.561 17.934 11.376 17.8103C11.191 17.6867 11.0468 17.511 10.9616 17.3055C10.8765 17.0999 10.8542 16.8737 10.8976 16.6555C10.941 16.4372 11.0482 16.2368 11.2055 16.0794C11.3628 15.9221 11.5633 15.815 11.7815 15.7716C11.9998 15.7281 12.226 15.7504 12.4315 15.8356C12.6371 15.9207 12.8128 16.0649 12.9364 16.2499C13.06 16.4349 13.126 16.6524 13.126 16.8749C13.126 17.1733 13.0075 17.4595 12.7965 17.6704C12.5855 17.8814 12.2994 17.9999 12.001 17.9999Z"
|
||||
fill="#FEC84B"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1">{toasterText}</div>
|
||||
<svg
|
||||
|
|
@ -49,7 +78,13 @@ export const Toaster = () => {
|
|||
className="absolute top-0 left-0"
|
||||
>
|
||||
<g filter="url(#filter0_f_376_2968)">
|
||||
<ellipse cx="-12" cy="28" rx="28" ry="13" fill="#6CE9A6" />
|
||||
<ellipse
|
||||
cx="-12"
|
||||
cy="28"
|
||||
rx="28"
|
||||
ry="13"
|
||||
fill={toasterType === 'success' ? '#6CE9A6' : '#FEC84B'}
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
|
|
@ -81,8 +116,8 @@ export const Toaster = () => {
|
|||
|
||||
export const useToaster = () => {
|
||||
return {
|
||||
show: useCallback((text: string) => {
|
||||
toaster.emit('show', text);
|
||||
show: useCallback((text: string, type?: 'success' | 'warning') => {
|
||||
toaster.emit('show', { text, type });
|
||||
}, []),
|
||||
};
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue