Merge branch 'preview' of github.com:makeplane/plane-ee into develop

This commit is contained in:
sriram veeraghanta
2024-08-19 19:29:04 +05:30
18 changed files with 213 additions and 393 deletions

View File

@@ -7,7 +7,7 @@ import jwt
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.validators import validate_email
from django.db.models import Count, F, Q
from django.db.models import Count
from django.utils import timezone
# Third party modules
@@ -33,9 +33,6 @@ from plane.db.models import (
from plane.utils.cache import invalidate_cache, invalidate_cache_directly
from plane.payment.bgtasks.member_sync_task import member_sync_task
from .. import BaseViewSet
from plane.payment.utils.member_payment_count import (
workspace_member_check,
)
class WorkspaceInvitationsViewset(BaseViewSet):

View File

@@ -36,9 +36,6 @@ from plane.db.models import (
)
from plane.utils.cache import cache_response, invalidate_cache
from plane.payment.bgtasks.member_sync_task import member_sync_task
from plane.payment.utils.member_payment_count import (
workspace_member_check,
)
from .. import BaseViewSet

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.14 on 2024-08-18 15:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ee', '0005_workspacelicense_has_activated_free_trial_and_more'),
]
operations = [
migrations.AddField(
model_name='workspacelicense',
name='subscription',
field=models.CharField(blank=True, max_length=255, null=True),
),
]

View File

@@ -55,6 +55,8 @@ class WorkspaceLicense(BaseModel):
has_activated_free_trial = models.BooleanField(default=False)
# is payment method added
has_added_payment_method = models.BooleanField(default=False)
# subscription
subscription = models.CharField(max_length=255, null=True, blank=True)
class Meta:
verbose_name = "Workspace License"

View File

@@ -4,17 +4,15 @@ import requests
# Django imports
from django.conf import settings
from django.db.models import F
from django.utils import timezone
# Third party imports
from celery import shared_task
# Module imports
from plane.db.models import WorkspaceMember, Workspace
from plane.ee.models import WorkspaceLicense
from plane.utils.exception_logger import log_exception
from plane.payment.utils.workspace_license_request import (
fetch_workspace_license,
resync_workspace_license,
)
@@ -63,76 +61,7 @@ def member_sync_task(slug):
response.raise_for_status()
# Refresh workspace license
updated_workspace_license = fetch_workspace_license(
workspace_id=workspace_id,
workspace_slug=slug,
free_seats=WorkspaceMember.objects.filter(
is_active=True,
workspace__slug=slug,
member__is_bot=False,
).count(),
)
# Update workspace license
workspace_license = WorkspaceLicense.objects.filter(
workspace=workspace
).first()
# If workspace license exists, update it
if workspace_license:
workspace_license.plane = updated_workspace_license["plan"]
workspace_license.recurring_interval = (
updated_workspace_license["interval"]
)
workspace_license.current_period_end_date = (
updated_workspace_license["current_period_end_date"]
)
workspace_license.purchased_seats = updated_workspace_license[
"purchased_seats"
]
workspace_license.free_seats = updated_workspace_license[
"free_seats"
]
workspace_license.is_cancelled = updated_workspace_license[
"is_cancelled"
]
workspace_license.is_offline_payment = (
updated_workspace_license["is_offline_payment"]
)
workspace_license.trial_end_date = updated_workspace_license[
"trial_end_date"
]
workspace_license.has_activated_free_trial = (
updated_workspace_license["has_activated_free_trial"]
)
workspace_license.has_added_payment_method = (
updated_workspace_license["has_added_payment_method"]
)
workspace_license.last_synced_at = timezone.now()
workspace_license.save()
# Else create a new workspace license
else:
WorkspaceLicense.objects.create(
workspace=workspace,
plan=updated_workspace_license["plan"],
recurring_interval=updated_workspace_license["interval"],
current_period_end_date=updated_workspace_license[
"current_period_end_date"
],
purchased_seats=updated_workspace_license["seats"],
is_cancelled=updated_workspace_license["is_cancelled"],
is_offline_payment=updated_workspace_license[
"is_offline_payment"
],
last_synced_at=timezone.now(),
has_activated_free_trial=updated_workspace_license[
"has_activated_free_trial"
],
has_added_payment_method=updated_workspace_license[
"has_added_payment_method"
],
)
resync_workspace_license(slug, force=True)
else:
return
except requests.exceptions.RequestException as e:

View File

@@ -3,6 +3,11 @@ import requests
# Django imports
from django.conf import settings
from django.utils import timezone
# Module imports
from plane.db.models import Workspace, WorkspaceMember
from plane.ee.models import WorkspaceLicense
def fetch_workspace_license(workspace_id, workspace_slug, free_seats=12):
@@ -24,3 +29,126 @@ def fetch_workspace_license(workspace_id, workspace_slug, free_seats=12):
response.raise_for_status()
response = response.json()
return response
def resync_workspace_license(workspace_slug, force=False):
# Fetch the workspace
workspace = Workspace.objects.get(slug=workspace_slug)
# Check if the license is present for the workspace
workspace_license = WorkspaceLicense.objects.filter(
workspace=workspace
).first()
# If the license is present, then check if the last sync is more than 1 hour
if workspace_license:
# If the last sync is more than 1 hour, then sync the license
if (
workspace_license.last_synced_at - timezone.now()
).total_seconds() > 3600 or force:
# Fetch the workspace license
response = fetch_workspace_license(
workspace_id=str(workspace.id),
workspace_slug=workspace_slug,
free_seats=WorkspaceMember.objects.filter(
is_active=True,
workspace__slug=workspace_slug,
member__is_bot=False,
).count(),
)
# Update the last synced time
workspace_license.last_synced_at = timezone.now()
workspace_license.is_cancelled = response.get(
"is_cancelled", False
)
workspace_license.free_seats = response.get("free_seats", 12)
workspace_license.purchased_seats = response.get(
"purchased_seats", 0
)
workspace_license.current_period_end_date = response.get(
"current_period_end_date"
)
workspace_license.recurring_interval = response.get("interval")
workspace_license.plan = response.get("plan")
workspace_license.is_offline_payment = response.get(
"is_offline_payment", False
)
workspace_license.trial_end_date = response.get("trial_end_date")
workspace_license.has_activated_free_trial = response.get(
"has_activated_free_trial", False
)
workspace_license.has_added_payment_method = response.get(
"has_added_payment_method", False
)
workspace_license.subscription = response.get("subscription")
workspace_license.save()
return {
"is_cancelled": workspace_license.is_cancelled,
"purchased_seats": workspace_license.purchased_seats,
"current_period_end_date": workspace_license.current_period_end_date,
"interval": workspace_license.recurring_interval,
"product": workspace_license.plan,
"is_offline_payment": workspace_license.is_offline_payment,
"trial_end_date": workspace_license.trial_end_date,
"has_activated_free_trial": workspace_license.has_activated_free_trial,
"has_added_payment_method": workspace_license.has_added_payment_method,
"subscription": workspace_license.subscription,
}
else:
return {
"is_cancelled": workspace_license.is_cancelled,
"purchased_seats": workspace_license.purchased_seats,
"current_period_end_date": workspace_license.current_period_end_date,
"interval": workspace_license.recurring_interval,
"product": workspace_license.plan,
"is_offline_payment": workspace_license.is_offline_payment,
"trial_end_date": workspace_license.trial_end_date,
"has_activated_free_trial": workspace_license.has_activated_free_trial,
"has_added_payment_method": workspace_license.has_added_payment_method,
"subscription": workspace_license.subscription,
}
# If the license is not present, then fetch the license from the payment server and create it
else:
# Fetch the workspace license
response = fetch_workspace_license(
workspace_id=str(workspace.id),
workspace_slug=workspace_slug,
free_seats=WorkspaceMember.objects.filter(
is_active=True,
workspace__slug=workspace_slug,
member__is_bot=False,
).count(),
)
# Create the workspace license
workspace_license = WorkspaceLicense.objects.create(
workspace=workspace,
is_cancelled=response.get("is_cancelled", False),
purchased_seats=response.get("purchased_seats", 0),
free_seats=response.get("free_seats", 12),
current_period_end_date=response.get("current_period_end_date"),
recurring_interval=response.get("interval"),
plan=response.get("plan"),
last_synced_at=timezone.now(),
trial_end_date=response.get("trial_end_date"),
has_activated_free_trial=response.get(
"has_activated_free_trial", False
),
has_added_payment_method=response.get(
"has_added_payment_method", False
),
subscription=response.get("subscription"),
)
# Return the workspace license
return {
"is_cancelled": workspace_license.is_cancelled,
"purchased_seats": workspace_license.purchased_seats,
"current_period_end_date": workspace_license.current_period_end_date,
"interval": workspace_license.recurring_interval,
"product": workspace_license.plan,
"is_offline_payment": workspace_license.is_offline_payment,
"trial_end_date": workspace_license.trial_end_date,
"has_activated_free_trial": workspace_license.has_activated_free_trial,
"has_added_payment_method": workspace_license.has_added_payment_method,
"subscription": workspace_license.subscription,
}

View File

@@ -3,8 +3,6 @@ import requests
# Django imports
from django.conf import settings
from django.db.models import CharField
from django.db.models.functions import Cast
from django.utils import timezone
from django.db.models import F
@@ -18,11 +16,11 @@ from .base import BaseAPIView
from plane.app.permissions.workspace import (
WorkspaceUserPermission,
)
from plane.db.models import WorkspaceMember, Workspace
from plane.db.models import WorkspaceMember
from plane.ee.models import WorkspaceLicense
from plane.utils.exception_logger import log_exception
from plane.payment.utils.workspace_license_request import (
fetch_workspace_license,
resync_workspace_license,
)
@@ -119,6 +117,7 @@ class WebsiteUserWorkspaceEndpoint(BaseAPIView):
"has_added_payment_method",
"current_period_end_date",
"is_offline_payment",
"subscription",
)
)
@@ -149,137 +148,9 @@ class WorkspaceProductEndpoint(BaseAPIView):
def get(self, request, slug):
try:
if settings.PAYMENT_SERVER_BASE_URL:
workspace = Workspace.objects.get(slug=slug)
# Check if the license is present for the workspace
workspace_license = WorkspaceLicense.objects.filter(
workspace=workspace
).first()
# If the license is present, then check if the last sync is more than 1 hour
if workspace_license:
# If the last sync is more than 1 hour, then sync the license
if (
workspace_license.last_synced_at - timezone.now()
).seconds > 3600:
response = fetch_workspace_license(
workspace_id=str(workspace.id),
workspace_slug=slug,
free_seats=WorkspaceMember.objects.filter(
is_active=True,
workspace__slug=slug,
member__is_bot=False,
).count(),
)
# Update the last synced time
workspace_license.last_synced_at = timezone.now()
workspace_license.is_cancelled = response.get(
"is_cancelled", False
)
workspace_license.free_seats = response.get(
"free_seats", 12
)
workspace_license.purchased_seats = response.get(
"purchased_seats", 0
)
workspace_license.current_period_end_date = (
response.get("current_period_end_date")
)
workspace_license.recurring_interval = response.get(
"interval"
)
workspace_license.plan = response.get("plan")
workspace_license.is_offline_payment = response.get(
"is_offline_payment", False
)
workspace_license.trial_end_date = response.get(
"trial_end_date"
)
workspace_license.has_activated_free_trial = (
response.get("has_activated_free_trial", False)
)
workspace_license.has_added_payment_method = (
response.get("has_added_payment_method", False)
)
workspace_license.save()
return Response(
{
"is_cancelled": workspace_license.is_cancelled,
"purchased_seats": workspace_license.purchased_seats,
"current_period_end_date": workspace_license.current_period_end_date,
"interval": workspace_license.recurring_interval,
"product": workspace_license.plan,
"is_offline_payment": workspace_license.is_offline_payment,
"trial_end_date": workspace_license.trial_end_date,
"has_activated_free_trial": workspace_license.has_activated_free_trial,
"has_added_payment_method": workspace_license.has_added_payment_method,
},
status=status.HTTP_200_OK,
)
else:
return Response(
{
"is_cancelled": workspace_license.is_cancelled,
"purchased_seats": workspace_license.purchased_seats,
"current_period_end_date": workspace_license.current_period_end_date,
"interval": workspace_license.recurring_interval,
"product": workspace_license.plan,
"is_offline_payment": workspace_license.is_offline_payment,
"trial_end_date": workspace_license.trial_end_date,
"has_activated_free_trial": workspace_license.has_activated_free_trial,
"has_added_payment_method": workspace_license.has_added_payment_method,
},
status=status.HTTP_200_OK,
)
# If the license is not present, then fetch the license from the payment server and create it
else:
# Fetch the workspace license
response = fetch_workspace_license(
workspace_id=str(workspace.id),
workspace_slug=slug,
free_seats=WorkspaceMember.objects.filter(
is_active=True,
workspace__slug=slug,
member__is_bot=False,
).count(),
)
# Create the workspace license
workspace_license = WorkspaceLicense.objects.create(
workspace=workspace,
is_cancelled=response.get("is_cancelled", False),
purchased_seats=response.get("purchased_seats", 0),
free_seats=response.get("free_seats", 12),
current_period_end_date=response.get(
"current_period_end_date"
),
recurring_interval=response.get("interval"),
plan=response.get("plan"),
last_synced_at=timezone.now(),
trial_end_date=response.get("trial_end_date"),
has_activated_free_trial=response.get(
"has_activated_free_trial", False
),
has_added_payment_method=response.get(
"has_added_payment_method", False
),
)
# Return the workspace license
return Response(
{
"is_cancelled": workspace_license.is_cancelled,
"purchased_seats": workspace_license.purchased_seats,
"current_period_end_date": workspace_license.current_period_end_date,
"interval": workspace_license.recurring_interval,
"product": workspace_license.plan,
"is_offline_payment": workspace_license.is_offline_payment,
"trial_end_date": workspace_license.trial_end_date,
"has_activated_free_trial": workspace_license.has_activated_free_trial,
"has_added_payment_method": workspace_license.has_added_payment_method,
},
status=status.HTTP_200_OK,
)
# Resync the workspace license
response = resync_workspace_license(workspace_slug=slug)
return Response(response, status=status.HTTP_200_OK)
else:
return Response(
{"error": "error fetching product details"},
@@ -295,84 +166,16 @@ class WorkspaceProductEndpoint(BaseAPIView):
class WorkspaceLicenseRefreshEndpoint(BaseAPIView):
def post(self, request, slug):
workspace = Workspace.objects.get(slug=slug)
# Check if the license is present for the workspace
workspace_license = WorkspaceLicense.objects.filter(
workspace=workspace
).first()
# If the license is present, then fetch the license from the payment server and update it
if workspace_license:
# Update the values in the workspace license
response = fetch_workspace_license(
workspace_id=str(workspace.id),
workspace_slug=slug,
free_seats=WorkspaceMember.objects.filter(
is_active=True,
workspace__slug=slug,
member__is_bot=False,
).count(),
)
workspace_license.is_cancelled = response.get(
"is_cancelled", False
)
workspace_license.free_seats = response.get("free_seats", 12)
workspace_license.purchased_seats = response.get(
"purchased_seats", 0
)
workspace_license.current_period_end_date = response.get(
"current_period_end_date"
)
workspace_license.recurring_interval = response.get("interval")
workspace_license.plan = response.get("plan")
workspace_license.trial_end_date = response.get("trial_end_date")
workspace_license.has_activated_free_trial = response.get(
"has_activated_free_trial", False
)
workspace_license.has_added_payment_method = response.get(
"has_added_payment_method", False
)
workspace_license.last_synced_at = timezone.now()
workspace_license.save()
# If the license is not present, then fetch the license from the payment server and create it
else:
# Fetch the workspace license
response = fetch_workspace_license(
workspace_id=str(workspace.id),
workspace_slug=slug,
free_seats=WorkspaceMember.objects.filter(
is_active=True,
workspace__slug=slug,
member__is_bot=False,
).count(),
)
# Create the workspace license
workspace_license = WorkspaceLicense.objects.create(
workspace=workspace,
is_cancelled=response.get("is_cancelled", False),
purchased_seats=response.get("purchased_seats", 0),
free_seats=response.get("free_seats", 12),
current_period_end_date=response.get(
"current_period_end_date"
),
recurring_interval=response.get("interval"),
plan=response.get("plan"),
last_synced_at=timezone.now(),
trial_end_date=response.get("trial_end_date"),
has_activated_free_trial=response.get(
"has_activated_free_trial", False
),
has_added_payment_method=response.get(
"has_added_payment_method", False
),
)
# Resync the workspace license
_ = resync_workspace_license(workspace_slug=slug, force=True)
# Return the response
return Response(status=status.HTTP_204_NO_CONTENT)
class WorkspaceLicenseSyncEndpoint(BaseAPIView):
"""This endpoint is used to sync the workspace license from the payment server"""
permission_classes = [
AllowAny,
]
@@ -427,6 +230,7 @@ class WorkspaceLicenseSyncEndpoint(BaseAPIView):
workspace_license.has_added_payment_method = request.data.get(
"has_added_payment_method", False
)
workspace_license.subscription = request.data.get("subscription")
workspace_license.save()
# If the workspace license is not present, then fetch the license from the payment server and create it
else:
@@ -449,6 +253,7 @@ class WorkspaceLicenseSyncEndpoint(BaseAPIView):
has_added_payment_method=request.data.get(
"has_added_payment_method", False
),
subscription=request.data.get("subscription"),
)
# Return the response

View File

@@ -3,7 +3,6 @@ import requests
# Django imports
from django.conf import settings
from django.utils import timezone
# Third party imports
from rest_framework import status
@@ -12,11 +11,10 @@ from rest_framework.response import Response
# Module imports
from .base import BaseAPIView
from plane.app.permissions.workspace import WorkspaceOwnerPermission
from plane.db.models import Workspace, WorkspaceMember
from plane.ee.models import WorkspaceLicense
from plane.db.models import Workspace
from plane.utils.exception_logger import log_exception
from plane.payment.utils.workspace_license_request import (
fetch_workspace_license,
resync_workspace_license,
)
@@ -102,90 +100,7 @@ class UpgradeSubscriptionEndpoint(BaseAPIView):
response.raise_for_status()
# Refetch the workspace license
workspace_license = WorkspaceLicense.objects.filter(
workspace=workspace
).first()
# Refetch the workspace license
workspace_license_response = fetch_workspace_license(
workspace_id=str(workspace.id),
workspace_slug=slug,
free_seats=WorkspaceMember.objects.filter(
is_active=True,
workspace__slug=slug,
member__is_bot=False,
).count(),
)
if workspace_license:
# Update the last synced time
workspace_license.last_synced_at = timezone.now()
workspace_license.is_cancelled = (
workspace_license_response.get("is_cancelled", False)
)
workspace_license.purchased_seats = (
workspace_license_response.get("purchased_seats", 0)
)
workspace_license.free_seats = (
workspace_license_response.get("free_seats", 12)
)
workspace_license.current_period_end_date = (
workspace_license_response.get(
"current_period_end_date"
)
)
workspace_license.recurring_interval = (
workspace_license_response.get("interval")
)
workspace_license.plan = workspace_license_response.get(
"plan"
)
workspace_license.trial_end_date = (
workspace_license_response.get("trial_end_date")
)
workspace_license.has_activated_free_trial = (
workspace_license_response.get(
"has_activated_free_trial"
)
)
workspace_license.has_added_payment_method = (
workspace_license_response.get(
"has_added_payment_method"
)
)
workspace_license.save()
else:
# Create a new workspace license
WorkspaceLicense.objects.create(
workspace=workspace,
last_synced_at=timezone.now(),
is_cancelled=workspace_license_response.get(
"is_cancelled", False
),
purchased_seats=workspace_license_response.get(
"purchased_seats", 0
),
free_seats=workspace_license_response.get(
"free_seats", 12
),
current_period_end_date=workspace_license_response.get(
"current_period_end_date"
),
recurring_interval=workspace_license_response.get(
"interval"
),
trial_end_date=workspace_license_response.get(
"trial_end_date"
),
plan=workspace_license_response.get("plan"),
has_activated_free_trial=workspace_license_response.get(
"has_activated_free_trial"
),
has_added_payment_method=workspace_license_response.get(
"has_added_payment_method"
),
)
resync_workspace_license(workspace_slug=workspace.slug)
# Return the response
return Response(status=status.HTTP_204_NO_CONTENT)
return Response(

View File

@@ -27,4 +27,5 @@ export type IWorkspaceProductSubscription = {
purchased_seats: number | undefined;
has_activated_free_trial: boolean;
has_added_payment_method: boolean;
subscription: string | undefined;
};

View File

@@ -48,7 +48,7 @@ const CloudUpgradePlanPage = observer(() => {
// fetch products
const { data: products, isLoading: isLoadingProduct } = useSWR(
workspaceSlug ? "CLOUD_PRO_PRODUCTS" : null,
workspaceSlug ? `CLOUD_PRO_PRODUCTS_${workspaceSlug?.toString()}` : null,
workspaceSlug ? () => paymentService.listProducts(workspaceSlug.toString()) : null,
{
errorRetryCount: 2,

View File

@@ -72,6 +72,7 @@ const CloudUpgradePage = observer(() => {
};
const workspaceSubscription = (workspace: TWorkspaceWithProductDetails): "FREE" | "PRO" | "TRIAL" => {
if (!workspace.subscription) return "FREE";
switch (workspace.product) {
case "PRO":
if (workspace.is_offline_payment) {

View File

@@ -98,7 +98,7 @@ export const ProjectFeaturesList: FC<Props> = observer((props) => {
</div>
<ToggleSwitch
value={Boolean(currentProjectDetails?.[featureItem.property as keyof IProject])}
value={Boolean(isWorklogEnabled && currentProjectDetails?.[featureItem.property as keyof IProject])}
onChange={() => handleSubmit(featureItemKey, featureItem.property)}
disabled={
!featureItem.isEnabled || !isAdmin || featureItem.property === "is_time_tracking_enabled"

View File

@@ -67,13 +67,10 @@ export const CloudEditionBadge = observer(() => {
</Loader>
);
// derived values
const isInTrialPeriod =
subscriptionDetail?.has_activated_free_trial && subscriptionDetail?.trial_end_date ? true : false;
const isTrialCompleted =
subscriptionDetail?.has_activated_free_trial && !subscriptionDetail?.trial_end_date ? true : false;
const renderButtonText = () => {
if (!subscriptionDetail.subscription) {
return "Upgrade to Pro";
}
switch (subscriptionDetail.product) {
case "FREE": {
if (subscriptionDetail?.has_activated_free_trial) {
@@ -98,6 +95,9 @@ export const CloudEditionBadge = observer(() => {
};
const showPaymentButton = () => {
if (!subscriptionDetail.subscription) {
return true;
}
switch (subscriptionDetail.product) {
case "FREE": {
return true;
@@ -134,7 +134,6 @@ export const CloudEditionBadge = observer(() => {
handleClose={() => toggleProPlanModal(false)}
yearlyPlan={false}
handleSuccessModal={() => setProPlanSuccessModalOpen(true)}
canFetchProducts={subscriptionDetail.product === "FREE" || isInTrialPeriod || isTrialCompleted}
/>
{showPaymentButton() && (

View File

@@ -26,6 +26,9 @@ export const PlaneCloudBilling: React.FC = observer(() => {
// derived values
const endDate = currentWorkspaceSubscribedPlanDetail?.current_period_end_date;
const isSubscriptionCancelled = currentWorkspaceSubscribedPlanDetail?.is_canceled;
const isInTrialPeriod =
!currentWorkspaceSubscribedPlanDetail?.has_added_payment_method &&
!!currentWorkspaceSubscribedPlanDetail?.trial_end_date;
const handleSubscriptionPageRedirection = () => {
setIsLoading(true);
@@ -102,15 +105,26 @@ export const PlaneCloudBilling: React.FC = observer(() => {
<div className="flex items-center gap-2">
<Image src={PlaneLogo} alt="Plane pro" width={24} height={24} />
<h4 className="text-2xl mb-1 leading-6 font-bold">Plane Pro</h4>
{isSubscriptionCancelled ? (
<div className="text-center text-sm text-red-500 font-medium">
(Expires on: {renderFormattedDate(endDate)})
</div>
) : (
<div className="text-center text-sm text-custom-text-200 font-medium">
(Renew on: {renderFormattedDate(endDate)})
</div>
{isInTrialPeriod && (
<>
<div className="flex-shrink-0 p-1 px-2 uppercase bg-custom-primary-100/20 text-custom-primary-100 text-xs rounded-full font-medium">
Free Trial
</div>
<div className="text-center text-sm text-custom-text-200 font-medium">
(Free trial ends on: {renderFormattedDate(currentWorkspaceSubscribedPlanDetail?.trial_end_date)})
</div>
</>
)}
{!isInTrialPeriod &&
(isSubscriptionCancelled ? (
<div className="text-center text-sm text-red-500 font-medium">
(Expires on: {renderFormattedDate(endDate)})
</div>
) : (
<div className="text-center text-sm text-custom-text-200 font-medium">
(Renew on: {renderFormattedDate(endDate)})
</div>
))}
</div>
{!currentWorkspaceSubscribedPlanDetail.is_offline_payment && (
<div>

View File

@@ -57,7 +57,7 @@ export const ProPlanCloudUpgradeModal: FC<ProPlanCloudUpgradeModalProps> = (prop
const isAdmin = currentWorkspaceRole === EUserWorkspaceRoles.ADMIN;
// fetch products
const { isLoading: isProductsAPILoading, data } = useSWR(
workspaceSlug && canFetchProducts ? "CLOUD_PAYMENT_PRODUCTS" : null,
workspaceSlug && canFetchProducts ? `CLOUD_PAYMENT_PRODUCTS_${workspaceSlug?.toString()}` : null,
workspaceSlug && canFetchProducts ? () => paymentService.listProducts(workspaceSlug.toString()) : null,
{
errorRetryCount: 2,

View File

@@ -1,13 +1,25 @@
import { useParams } from "next/navigation";
import { Control, Controller } from "react-hook-form";
// types
import { IProjectView, IWorkspaceView } from "@plane/types";
// components
import { AccessField } from "@/components/common/access-field";
// constants
import { VIEW_ACCESS_SPECIFIERS } from "@/constants/views";
import { useFlag } from "@/plane-web/hooks/store";
type Props = {
control: Control<IProjectView, any> | Control<IWorkspaceView, any>;
};
export const AccessController = (props: Props) => {
const { control } = props;
// router
const { workspaceSlug } = useParams();
// plane web hooks
const isPrivateViewsEnabled = useFlag(workspaceSlug?.toString(), "VIEW_ACCESS_PRIVATE");
if (!isPrivateViewsEnabled) return null;
return (
<Controller
control={control as Control<IProjectView, any>}

View File

@@ -63,6 +63,7 @@ export class WorkspaceSubscriptionStore implements IWorkspaceSubscriptionStore {
purchased_seats: response?.purchased_seats || 0,
has_activated_free_trial: response?.has_activated_free_trial || false,
has_added_payment_method: response?.has_added_payment_method || false,
subscription: response?.subscription || undefined,
});
});
return response;

View File

@@ -12,4 +12,5 @@ export type TWorkspaceWithProductDetails = {
has_added_payment_method: boolean;
trial_end_date: string;
is_offline_payment: boolean;
subscription: string;
};