mirror of
https://github.com/makeplane/plane.git
synced 2025-12-29 00:24:56 +01:00
fix: merge conflicts resolved
This commit is contained in:
28
apiserver/bin/takeoff.cloud
Normal file
28
apiserver/bin/takeoff.cloud
Normal file
@@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
python manage.py wait_for_db
|
||||
python manage.py migrate
|
||||
|
||||
# Create the default bucket
|
||||
#!/bin/bash
|
||||
|
||||
# Collect system information
|
||||
HOSTNAME=$(hostname)
|
||||
MAC_ADDRESS=$(ip link show | awk '/ether/ {print $2}' | head -n 1)
|
||||
CPU_INFO=$(cat /proc/cpuinfo)
|
||||
MEMORY_INFO=$(free -h)
|
||||
DISK_INFO=$(df -h)
|
||||
|
||||
# Concatenate information and compute SHA-256 hash
|
||||
SIGNATURE=$(echo "$HOSTNAME$MAC_ADDRESS$CPU_INFO$MEMORY_INFO$DISK_INFO" | sha256sum | awk '{print $1}')
|
||||
|
||||
# Export the variables
|
||||
export MACHINE_SIGNATURE=$SIGNATURE
|
||||
|
||||
# Register instance
|
||||
python manage.py setup_instance $INSTANCE_ADMIN_EMAIL
|
||||
|
||||
# Create the default bucket
|
||||
python manage.py create_bucket
|
||||
|
||||
exec gunicorn -w $GUNICORN_WORKERS -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:${PORT:-8000} --max-requests 1200 --max-requests-jitter 1000 --access-logfile -
|
||||
@@ -25,6 +25,7 @@ from plane.db.models import (
|
||||
User,
|
||||
IssueProperty,
|
||||
)
|
||||
from plane.bgtasks.user_welcome_task import send_welcome_slack
|
||||
|
||||
|
||||
@shared_task
|
||||
@@ -54,6 +55,15 @@ def service_importer(service, importer_id):
|
||||
ignore_conflicts=True,
|
||||
)
|
||||
|
||||
_ = [
|
||||
send_welcome_slack.delay(
|
||||
str(user.id),
|
||||
True,
|
||||
f"{user.email} was imported to Plane from {service}",
|
||||
)
|
||||
for user in new_users
|
||||
]
|
||||
|
||||
workspace_users = User.objects.filter(
|
||||
email__in=[
|
||||
user.get("email").strip().lower()
|
||||
|
||||
36
apiserver/plane/bgtasks/user_welcome_task.py
Normal file
36
apiserver/plane/bgtasks/user_welcome_task.py
Normal file
@@ -0,0 +1,36 @@
|
||||
# Django imports
|
||||
from django.conf import settings
|
||||
|
||||
# Third party imports
|
||||
from celery import shared_task
|
||||
from sentry_sdk import capture_exception
|
||||
from slack_sdk import WebClient
|
||||
from slack_sdk.errors import SlackApiError
|
||||
|
||||
# Module imports
|
||||
from plane.db.models import User
|
||||
|
||||
|
||||
@shared_task
|
||||
def send_welcome_slack(user_id, created, message):
|
||||
try:
|
||||
instance = User.objects.get(pk=user_id)
|
||||
|
||||
if created and not instance.is_bot:
|
||||
# Send message on slack as well
|
||||
if settings.SLACK_BOT_TOKEN:
|
||||
client = WebClient(token=settings.SLACK_BOT_TOKEN)
|
||||
try:
|
||||
_ = client.chat_postMessage(
|
||||
channel="#trackers",
|
||||
text=message,
|
||||
)
|
||||
except SlackApiError as e:
|
||||
print(f"Got an error: {e.response['error']}")
|
||||
return
|
||||
except Exception as e:
|
||||
# Print logs if in DEBUG mode
|
||||
if settings.DEBUG:
|
||||
print(e)
|
||||
capture_exception(e)
|
||||
return
|
||||
@@ -20,6 +20,18 @@ from plane.db.models import Workspace, WorkspaceMemberInvite, User
|
||||
from plane.license.utils.instance_value import get_email_configuration
|
||||
|
||||
|
||||
def push_updated_to_slack(workspace, workspace_member_invite):
|
||||
# Send message on slack as well
|
||||
client = WebClient(token=settings.SLACK_BOT_TOKEN)
|
||||
try:
|
||||
_ = client.chat_postMessage(
|
||||
channel="#trackers",
|
||||
text=f"{workspace_member_invite.email} has been invited to {workspace.name} as a {workspace_member_invite.role}",
|
||||
)
|
||||
except SlackApiError as e:
|
||||
print(f"Got an error: {e.response['error']}")
|
||||
|
||||
|
||||
@shared_task
|
||||
def workspace_invitation(email, workspace_id, token, current_site, invitor):
|
||||
try:
|
||||
@@ -82,6 +94,10 @@ def workspace_invitation(email, workspace_id, token, current_site, invitor):
|
||||
msg.attach_alternative(html_content, "text/html")
|
||||
msg.send()
|
||||
|
||||
# Send message on slack as well
|
||||
if settings.SLACK_BOT_TOKEN:
|
||||
push_updated_to_slack(workspace, workspace_member_invite)
|
||||
|
||||
return
|
||||
except (Workspace.DoesNotExist, WorkspaceMemberInvite.DoesNotExist) as e:
|
||||
print("Workspace or WorkspaceMember Invite Does not exists")
|
||||
|
||||
@@ -12,7 +12,14 @@ from django.contrib.auth.models import (
|
||||
PermissionsMixin,
|
||||
)
|
||||
from django.utils import timezone
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
from django.conf import settings
|
||||
|
||||
# Third party imports
|
||||
from sentry_sdk import capture_exception
|
||||
from slack_sdk import WebClient
|
||||
from slack_sdk.errors import SlackApiError
|
||||
|
||||
def get_default_onboarding():
|
||||
return {
|
||||
@@ -134,3 +141,23 @@ class User(AbstractBaseUser, PermissionsMixin):
|
||||
self.is_staff = True
|
||||
|
||||
super(User, self).save(*args, **kwargs)
|
||||
|
||||
|
||||
@receiver(post_save, sender=User)
|
||||
def send_welcome_slack(sender, instance, created, **kwargs):
|
||||
try:
|
||||
if created and not instance.is_bot:
|
||||
# Send message on slack as well
|
||||
if settings.SLACK_BOT_TOKEN:
|
||||
client = WebClient(token=settings.SLACK_BOT_TOKEN)
|
||||
try:
|
||||
_ = client.chat_postMessage(
|
||||
channel="#trackers",
|
||||
text=f"New user {instance.email} has signed up and begun the onboarding journey.",
|
||||
)
|
||||
except SlackApiError as e:
|
||||
print(f"Got an error: {e.response['error']}")
|
||||
return
|
||||
except Exception as e:
|
||||
capture_exception(e)
|
||||
return
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
# Python imports
|
||||
import json
|
||||
import secrets
|
||||
import uuid
|
||||
|
||||
# Django imports
|
||||
from django.contrib.auth.hashers import make_password
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.utils import timezone
|
||||
|
||||
# Module imports
|
||||
from plane.license.models import Instance, InstanceAdmin
|
||||
from plane.db.models import User
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Check if instance in registered else register"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
# Positional argument
|
||||
parser.add_argument("admin_email", type=str, help="Admin Email")
|
||||
|
||||
def handle(self, *args, **options):
|
||||
with open("package.json", "r") as file:
|
||||
# Load JSON content from the file
|
||||
data = json.load(file)
|
||||
|
||||
admin_email = options.get("admin_email", False)
|
||||
|
||||
if not admin_email:
|
||||
raise CommandError("admin email is required")
|
||||
|
||||
user_count = User.objects.filter(is_bot=False).count()
|
||||
|
||||
user = User.objects.filter(email=admin_email).first()
|
||||
if user is None:
|
||||
user = User.objects.create(
|
||||
email=admin_email,
|
||||
username=uuid.uuid4().hex,
|
||||
password=make_password(uuid.uuid4().hex),
|
||||
)
|
||||
|
||||
try:
|
||||
# Check if the instance is registered
|
||||
instance = Instance.objects.first()
|
||||
|
||||
if instance is None:
|
||||
instance = Instance.objects.create(
|
||||
instance_name="Plane Enterprise",
|
||||
instance_id=secrets.token_hex(12),
|
||||
license_key=None,
|
||||
api_key=secrets.token_hex(8),
|
||||
version=data.get("version"),
|
||||
last_checked_at=timezone.now(),
|
||||
user_count=user_count,
|
||||
is_verified=True,
|
||||
is_setup_done=True,
|
||||
is_signup_screen_visited=True,
|
||||
)
|
||||
|
||||
# Get or create an instance admin
|
||||
_, created = InstanceAdmin.objects.get_or_create(
|
||||
user=user, instance=instance, role=20, is_verified=True
|
||||
)
|
||||
|
||||
if not created:
|
||||
self.stdout.write(
|
||||
self.style.WARNING("given email is already an instance admin")
|
||||
)
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(f"Successful"))
|
||||
except Exception as e:
|
||||
print(e)
|
||||
raise CommandError("Failure")
|
||||
@@ -314,7 +314,7 @@ if bool(os.environ.get("SENTRY_DSN", False)) and os.environ.get(
|
||||
|
||||
# Application Envs
|
||||
PROXY_BASE_URL = os.environ.get("PROXY_BASE_URL", False) # For External
|
||||
|
||||
SLACK_BOT_TOKEN = os.environ.get("SLACK_BOT_TOKEN", False)
|
||||
FILE_SIZE_LIMIT = int(os.environ.get("FILE_SIZE_LIMIT", 5242880))
|
||||
|
||||
# Unsplash Access key
|
||||
|
||||
17
turbo.json
17
turbo.json
@@ -17,17 +17,14 @@
|
||||
"NEXT_PUBLIC_POSTHOG_KEY",
|
||||
"NEXT_PUBLIC_POSTHOG_HOST",
|
||||
"JITSU_TRACKER_ACCESS_KEY",
|
||||
"JITSU_TRACKER_HOST"
|
||||
"JITSU_TRACKER_HOST",
|
||||
"NEXT_PUBLIC_PRO_PLAN_MONTHLY_REDIRECT_URL",
|
||||
"NEXT_PUBLIC_PRO_PLAN_YEARLY_REDIRECT_URL"
|
||||
],
|
||||
"pipeline": {
|
||||
"build": {
|
||||
"dependsOn": [
|
||||
"^build"
|
||||
],
|
||||
"outputs": [
|
||||
".next/**",
|
||||
"dist/**"
|
||||
]
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": [".next/**", "dist/**"]
|
||||
},
|
||||
"develop": {
|
||||
"cache": false,
|
||||
@@ -44,9 +41,7 @@
|
||||
]
|
||||
},
|
||||
"test": {
|
||||
"dependsOn": [
|
||||
"^build"
|
||||
],
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": []
|
||||
},
|
||||
"lint": {
|
||||
|
||||
1
web/components/license/index.ts
Normal file
1
web/components/license/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./pro-plan-modal";
|
||||
190
web/components/license/pro-plan-modal.tsx
Normal file
190
web/components/license/pro-plan-modal.tsx
Normal file
@@ -0,0 +1,190 @@
|
||||
import { FC, Fragment, useState } from "react";
|
||||
import { Dialog, Transition, Tab } from "@headlessui/react";
|
||||
import { CheckCircle } from "lucide-react";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
function classNames(...classes: any[]) {
|
||||
return classes.filter(Boolean).join(" ");
|
||||
}
|
||||
|
||||
const PRICING_CATEGORIES = ["Monthly", "Yearly"];
|
||||
|
||||
const MONTHLY_PLAN_ITEMS = [
|
||||
"White-glove onboarding for your use-cases",
|
||||
"Bespoke implementation",
|
||||
"Priority integrations",
|
||||
"Priority Support and SLAs",
|
||||
"Early access to all paid features",
|
||||
"Locked-in discount for a whole year",
|
||||
];
|
||||
|
||||
const YEARLY_PLAN_ITEMS = [
|
||||
"White-glove onboarding for your use-cases",
|
||||
"Bespoke implementation",
|
||||
"Priority integrations",
|
||||
"Priority Support and SLAs",
|
||||
"Early access to all paid features",
|
||||
"Tiered discounts for the second and third years",
|
||||
];
|
||||
|
||||
export type ProPlanModalProps = {
|
||||
isOpen: boolean;
|
||||
handleClose: () => void;
|
||||
};
|
||||
|
||||
export const ProPlanModal: FC<ProPlanModalProps> = (props) => {
|
||||
const { isOpen, handleClose } = props;
|
||||
// store
|
||||
const {
|
||||
trackEvent: { captureEvent },
|
||||
} = useMobxStore();
|
||||
// states
|
||||
const [tabIndex, setTabIndex] = useState(0);
|
||||
|
||||
const handleProPlaneMonthRedirection = () => {
|
||||
if (process.env.NEXT_PUBLIC_PRO_PLAN_MONTHLY_REDIRECT_URL) {
|
||||
window.open(process.env.NEXT_PUBLIC_PRO_PLAN_MONTHLY_REDIRECT_URL, "_blank");
|
||||
captureEvent("pro_plan_modal_month_redirection");
|
||||
}
|
||||
};
|
||||
|
||||
const handleProPlanYearlyRedirection = () => {
|
||||
if (process.env.NEXT_PUBLIC_PRO_PLAN_YEARLY_REDIRECT_URL) {
|
||||
window.open(process.env.NEXT_PUBLIC_PRO_PLAN_YEARLY_REDIRECT_URL, "_blank");
|
||||
captureEvent("pro_plan_modal_yearly_redirection");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-50" onClose={handleClose}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-custom-backdrop" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="w-full max-w-lg transform overflow-hidden rounded-2xl bg-custom-background-100 p-6 text-left align-middle shadow-xl transition-all border-[0.5px] border-custom-border-100">
|
||||
<Dialog.Title as="h2" className="text-2xl font-bold leading-6 mt-4 flex justify-center items-center">
|
||||
Early-adopter pricing for believers
|
||||
</Dialog.Title>
|
||||
<div className="mt-2 mb-5">
|
||||
<p className="text-center text-sm mb-6 px-10 text-custom-text-200">
|
||||
Build Plane to your specs. You decide what we prioritize and build for everyone. Also get tailored
|
||||
onboarding + implementation and priority support.
|
||||
</p>
|
||||
<Tab.Group>
|
||||
<div className="flex w-full justify-center">
|
||||
<Tab.List className="flex space-x-1 rounded-xl bg-custom-background-80 p-1 w-[72%]">
|
||||
{PRICING_CATEGORIES.map((category, index) => (
|
||||
<Tab
|
||||
key={category}
|
||||
className={({ selected }) =>
|
||||
classNames(
|
||||
"w-full rounded-lg py-2 text-sm font-medium leading-5",
|
||||
"ring-white/60 ring-offset-2 ring-offset-custom-primary-90 focus:outline-none",
|
||||
selected
|
||||
? "bg-custom-background-100 text-custom-primary-100 shadow"
|
||||
: "hover:bg-custom-background-80 text-custom-text-300 hover:text-custom-text-100"
|
||||
)
|
||||
}
|
||||
onClick={() => setTabIndex(index)}
|
||||
>
|
||||
<>
|
||||
{category}
|
||||
{category === "Yearly" && (
|
||||
<span className="bg-custom-primary-100 text-white rounded-full px-2 py-1 ml-1 text-xs">
|
||||
-28%
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
</Tab>
|
||||
))}
|
||||
</Tab.List>
|
||||
</div>
|
||||
|
||||
<Tab.Panels className="mt-2">
|
||||
<Tab.Panel className={classNames("rounded-xl bg-custom-background-100 p-3")}>
|
||||
<p className="ml-4 text-4xl font-bold mb-2">
|
||||
$7
|
||||
<span className="text-sm ml-3 text-custom-text-300">/user/month</span>
|
||||
</p>
|
||||
<ul>
|
||||
{MONTHLY_PLAN_ITEMS.map((item) => (
|
||||
<li key={item} className="relative rounded-md p-3 flex">
|
||||
<p className="text-sm font-medium leading-5 flex items-center">
|
||||
<CheckCircle className="h-4 w-4 mr-4" />
|
||||
<span>{item}</span>
|
||||
</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="flex justify-center w-full">
|
||||
<div className="relative inline-flex group mt-8">
|
||||
<div className="absolute transition-all duration-1000 opacity-50 -inset-px bg-gradient-to-r from-[#44BCFF] via-[#FF44EC] to-[#FF675E] rounded-xl blur-lg group-hover:opacity-100 group-hover:-inset-1 group-hover:duration-200 animate-tilt" />
|
||||
<button
|
||||
type="button"
|
||||
className="relative inline-flex items-center justify-center px-8 py-4 text-sm font-medium border-custom-border-100 border-[1.5px] transition-all duration-200 bg-custom-background-100 rounded-xl focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-custom-border-200"
|
||||
onClick={handleProPlaneMonthRedirection}
|
||||
>
|
||||
Become Early Adopter
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Tab.Panel>
|
||||
<Tab.Panel className={classNames("rounded-xl bg-custom-background-100 p-3")}>
|
||||
<p className="ml-4 text-4xl font-bold mb-2">
|
||||
$5
|
||||
<span className="text-sm ml-3 text-custom-text-300">/user/month</span>
|
||||
</p>
|
||||
<ul>
|
||||
{YEARLY_PLAN_ITEMS.map((item) => (
|
||||
<li key={item} className="relative rounded-md p-3 flex">
|
||||
<p className="text-sm font-medium leading-5 flex items-center">
|
||||
<CheckCircle className="h-4 w-4 mr-4" />
|
||||
<span>{item}</span>
|
||||
</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="flex justify-center w-full">
|
||||
<div className="relative inline-flex group mt-8">
|
||||
<div className="absolute transition-all duration-1000 opacity-50 -inset-px bg-gradient-to-r from-[#44BCFF] via-[#FF44EC] to-[#FF675E] rounded-xl blur-lg group-hover:opacity-100 group-hover:-inset-1 group-hover:duration-200 animate-tilt" />
|
||||
<button
|
||||
type="button"
|
||||
className="relative inline-flex items-center justify-center px-8 py-4 text-sm font-medium border-custom-border-100 border-[1.5px] transition-all duration-200 bg-custom-background-100 rounded-xl focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-custom-border-200"
|
||||
onClick={handleProPlanYearlyRedirection}
|
||||
>
|
||||
Become Early Adopter
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Tab.Panel>
|
||||
</Tab.Panels>
|
||||
</Tab.Group>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
@@ -7,9 +7,11 @@ import { useApplication } from "hooks/store";
|
||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||
// icons
|
||||
import { FileText, HelpCircle, MessagesSquare, MoveLeft, Zap } from "lucide-react";
|
||||
import { DiscordIcon, GithubIcon } from "@plane/ui";
|
||||
import { DiscordIcon, GithubIcon, Button } from "@plane/ui";
|
||||
// assets
|
||||
import packageJson from "package.json";
|
||||
// components
|
||||
import { ProPlanModal } from "components/license";
|
||||
|
||||
const helpOptions = [
|
||||
{
|
||||
@@ -40,10 +42,13 @@ export interface WorkspaceHelpSectionProps {
|
||||
}
|
||||
|
||||
export const WorkspaceHelpSection: React.FC<WorkspaceHelpSectionProps> = observer(() => {
|
||||
// states
|
||||
const [isProPlanModalOpen, setIsProPlanModalOpen] = useState(false);
|
||||
// store hooks
|
||||
const {
|
||||
theme: { sidebarCollapsed, toggleSidebar },
|
||||
commandPalette: { toggleShortcutModal },
|
||||
eventTracker: { captureEvent },
|
||||
} = useApplication();
|
||||
// states
|
||||
const [isNeedHelpOpen, setIsNeedHelpOpen] = useState(false);
|
||||
@@ -54,17 +59,27 @@ export const WorkspaceHelpSection: React.FC<WorkspaceHelpSectionProps> = observe
|
||||
|
||||
const isCollapsed = sidebarCollapsed || false;
|
||||
|
||||
const handleProPlanModalOpen = () => {
|
||||
setIsProPlanModalOpen(true);
|
||||
captureEvent("pro_plan_modal_opened");
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ProPlanModal isOpen={isProPlanModalOpen} handleClose={() => setIsProPlanModalOpen(false)} />
|
||||
<div
|
||||
className={`flex w-full items-center justify-between gap-1 self-baseline border-t border-custom-border-200 bg-custom-sidebar-background-100 px-4 py-2 ${
|
||||
isCollapsed ? "flex-col" : ""
|
||||
}`}
|
||||
>
|
||||
{!isCollapsed && (
|
||||
<div className="w-1/2 cursor-default rounded-md bg-green-500/10 px-2.5 py-1.5 text-center text-sm font-medium text-green-500 outline-none">
|
||||
Free Plan
|
||||
</div>
|
||||
<Button
|
||||
variant="outline-primary"
|
||||
className="w-1/2 cursor-pointer rounded-2xl px-2.5 py-1.5 text-center text-sm font-medium outline-none"
|
||||
onClick={handleProPlanModalOpen}
|
||||
>
|
||||
Plane Pro
|
||||
</Button>
|
||||
)}
|
||||
<div className={`flex items-center gap-1 ${isCollapsed ? "flex-col justify-center" : "w-1/2 justify-evenly"}`}>
|
||||
<button
|
||||
|
||||
@@ -11,6 +11,7 @@ export interface IEventTrackerStore {
|
||||
payload: object | [] | null,
|
||||
group?: { isGrouping: boolean | null; groupType: string | null; groupId: string | null } | null
|
||||
) => void;
|
||||
captureEvent: (eventName: string, payload?: any) => void;
|
||||
}
|
||||
|
||||
export class EventTrackerStore implements IEventTrackerStore {
|
||||
@@ -76,4 +77,12 @@ export class EventTrackerStore implements IEventTrackerStore {
|
||||
}
|
||||
this.setTrackElement("");
|
||||
};
|
||||
|
||||
captureEvent = (eventName: string, payload?: any) => {
|
||||
try {
|
||||
posthog?.capture(eventName, payload);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user