Compare commits
19
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38bdcab1c3 | ||
|
|
56701442c2 | ||
|
|
4a061332c4 | ||
|
|
7b116b7af0 | ||
|
|
155857c370 | ||
|
|
fe68d0f25f | ||
|
|
10bca485cf | ||
|
|
9fc0fa79a1 | ||
|
|
992dac3447 | ||
|
|
8ad152940a | ||
|
|
96f95ac1c4 | ||
|
|
c54aa7ad3d | ||
|
|
f8df5c0f2e | ||
|
|
8605789ca6 | ||
|
|
3cb427f839 | ||
|
|
cdd6a7be10 | ||
|
|
9704f21c6e | ||
|
|
0e369f87ac | ||
|
|
d52cce150f |
@@ -13,6 +13,7 @@ from plane.app.views import (
|
||||
ProjectAssetEndpoint,
|
||||
ProjectBulkAssetEndpoint,
|
||||
AssetCheckEndpoint,
|
||||
DuplicateAssetEndpoint,
|
||||
WorkspaceAssetDownloadEndpoint,
|
||||
ProjectAssetDownloadEndpoint,
|
||||
)
|
||||
@@ -91,6 +92,11 @@ urlpatterns = [
|
||||
AssetCheckEndpoint.as_view(),
|
||||
name="asset-check",
|
||||
),
|
||||
path(
|
||||
"assets/v2/workspaces/<str:slug>/duplicate-assets/<uuid:asset_id>/",
|
||||
DuplicateAssetEndpoint.as_view(),
|
||||
name="duplicate-assets",
|
||||
),
|
||||
path(
|
||||
"assets/v2/workspaces/<str:slug>/download/<uuid:asset_id>/",
|
||||
WorkspaceAssetDownloadEndpoint.as_view(),
|
||||
|
||||
@@ -107,6 +107,7 @@ from .asset.v2 import (
|
||||
ProjectAssetEndpoint,
|
||||
ProjectBulkAssetEndpoint,
|
||||
AssetCheckEndpoint,
|
||||
DuplicateAssetEndpoint,
|
||||
WorkspaceAssetDownloadEndpoint,
|
||||
ProjectAssetDownloadEndpoint,
|
||||
)
|
||||
|
||||
@@ -19,6 +19,7 @@ from plane.settings.storage import S3Storage
|
||||
from plane.app.permissions import allow_permission, ROLE
|
||||
from plane.utils.cache import invalidate_cache_directly
|
||||
from plane.bgtasks.storage_metadata_task import get_asset_object_metadata
|
||||
from plane.throttles.asset import AssetRateThrottle
|
||||
|
||||
|
||||
class UserAssetsV2Endpoint(BaseAPIView):
|
||||
@@ -44,7 +45,9 @@ class UserAssetsV2Endpoint(BaseAPIView):
|
||||
# Save the new avatar
|
||||
user.avatar_asset_id = asset_id
|
||||
user.save()
|
||||
invalidate_cache_directly(path="/api/users/me/", url_params=False, user=True, request=request)
|
||||
invalidate_cache_directly(
|
||||
path="/api/users/me/", url_params=False, user=True, request=request
|
||||
)
|
||||
invalidate_cache_directly(
|
||||
path="/api/users/me/settings/",
|
||||
url_params=False,
|
||||
@@ -62,7 +65,9 @@ class UserAssetsV2Endpoint(BaseAPIView):
|
||||
# Save the new cover image
|
||||
user.cover_image_asset_id = asset_id
|
||||
user.save()
|
||||
invalidate_cache_directly(path="/api/users/me/", url_params=False, user=True, request=request)
|
||||
invalidate_cache_directly(
|
||||
path="/api/users/me/", url_params=False, user=True, request=request
|
||||
)
|
||||
invalidate_cache_directly(
|
||||
path="/api/users/me/settings/",
|
||||
url_params=False,
|
||||
@@ -78,7 +83,9 @@ class UserAssetsV2Endpoint(BaseAPIView):
|
||||
user = User.objects.get(id=asset.user_id)
|
||||
user.avatar_asset_id = None
|
||||
user.save()
|
||||
invalidate_cache_directly(path="/api/users/me/", url_params=False, user=True, request=request)
|
||||
invalidate_cache_directly(
|
||||
path="/api/users/me/", url_params=False, user=True, request=request
|
||||
)
|
||||
invalidate_cache_directly(
|
||||
path="/api/users/me/settings/",
|
||||
url_params=False,
|
||||
@@ -91,7 +98,9 @@ class UserAssetsV2Endpoint(BaseAPIView):
|
||||
user = User.objects.get(id=asset.user_id)
|
||||
user.cover_image_asset_id = None
|
||||
user.save()
|
||||
invalidate_cache_directly(path="/api/users/me/", url_params=False, user=True, request=request)
|
||||
invalidate_cache_directly(
|
||||
path="/api/users/me/", url_params=False, user=True, request=request
|
||||
)
|
||||
invalidate_cache_directly(
|
||||
path="/api/users/me/settings/",
|
||||
url_params=False,
|
||||
@@ -151,7 +160,9 @@ class UserAssetsV2Endpoint(BaseAPIView):
|
||||
# Get the presigned URL
|
||||
storage = S3Storage(request=request)
|
||||
# Generate a presigned URL to share an S3 object
|
||||
presigned_url = storage.generate_presigned_post(object_name=asset_key, file_type=type, file_size=size_limit)
|
||||
presigned_url = storage.generate_presigned_post(
|
||||
object_name=asset_key, file_type=type, file_size=size_limit
|
||||
)
|
||||
# Return the presigned URL
|
||||
return Response(
|
||||
{
|
||||
@@ -188,7 +199,9 @@ class UserAssetsV2Endpoint(BaseAPIView):
|
||||
asset.is_deleted = True
|
||||
asset.deleted_at = timezone.now()
|
||||
# get the entity and save the asset id for the request field
|
||||
self.entity_asset_delete(entity_type=asset.entity_type, asset=asset, request=request)
|
||||
self.entity_asset_delete(
|
||||
entity_type=asset.entity_type, asset=asset, request=request
|
||||
)
|
||||
asset.save(update_fields=["is_deleted", "deleted_at"])
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
@@ -252,14 +265,18 @@ class WorkspaceFileAssetEndpoint(BaseAPIView):
|
||||
workspace.logo = ""
|
||||
workspace.logo_asset_id = asset_id
|
||||
workspace.save()
|
||||
invalidate_cache_directly(path="/api/workspaces/", url_params=False, user=False, request=request)
|
||||
invalidate_cache_directly(
|
||||
path="/api/workspaces/", url_params=False, user=False, request=request
|
||||
)
|
||||
invalidate_cache_directly(
|
||||
path="/api/users/me/workspaces/",
|
||||
url_params=False,
|
||||
user=True,
|
||||
request=request,
|
||||
)
|
||||
invalidate_cache_directly(path="/api/instances/", url_params=False, user=False, request=request)
|
||||
invalidate_cache_directly(
|
||||
path="/api/instances/", url_params=False, user=False, request=request
|
||||
)
|
||||
return
|
||||
|
||||
# Project Cover
|
||||
@@ -286,14 +303,18 @@ class WorkspaceFileAssetEndpoint(BaseAPIView):
|
||||
return
|
||||
workspace.logo_asset_id = None
|
||||
workspace.save()
|
||||
invalidate_cache_directly(path="/api/workspaces/", url_params=False, user=False, request=request)
|
||||
invalidate_cache_directly(
|
||||
path="/api/workspaces/", url_params=False, user=False, request=request
|
||||
)
|
||||
invalidate_cache_directly(
|
||||
path="/api/users/me/workspaces/",
|
||||
url_params=False,
|
||||
user=True,
|
||||
request=request,
|
||||
)
|
||||
invalidate_cache_directly(path="/api/instances/", url_params=False, user=False, request=request)
|
||||
invalidate_cache_directly(
|
||||
path="/api/instances/", url_params=False, user=False, request=request
|
||||
)
|
||||
return
|
||||
# Project Cover
|
||||
elif entity_type == FileAsset.EntityTypeContext.PROJECT_COVER:
|
||||
@@ -354,13 +375,17 @@ class WorkspaceFileAssetEndpoint(BaseAPIView):
|
||||
workspace=workspace,
|
||||
created_by=request.user,
|
||||
entity_type=entity_type,
|
||||
**self.get_entity_id_field(entity_type=entity_type, entity_id=entity_identifier),
|
||||
**self.get_entity_id_field(
|
||||
entity_type=entity_type, entity_id=entity_identifier
|
||||
),
|
||||
)
|
||||
|
||||
# Get the presigned URL
|
||||
storage = S3Storage(request=request)
|
||||
# Generate a presigned URL to share an S3 object
|
||||
presigned_url = storage.generate_presigned_post(object_name=asset_key, file_type=type, file_size=size_limit)
|
||||
presigned_url = storage.generate_presigned_post(
|
||||
object_name=asset_key, file_type=type, file_size=size_limit
|
||||
)
|
||||
# Return the presigned URL
|
||||
return Response(
|
||||
{
|
||||
@@ -397,7 +422,9 @@ class WorkspaceFileAssetEndpoint(BaseAPIView):
|
||||
asset.is_deleted = True
|
||||
asset.deleted_at = timezone.now()
|
||||
# get the entity and save the asset id for the request field
|
||||
self.entity_asset_delete(entity_type=asset.entity_type, asset=asset, request=request)
|
||||
self.entity_asset_delete(
|
||||
entity_type=asset.entity_type, asset=asset, request=request
|
||||
)
|
||||
asset.save(update_fields=["is_deleted", "deleted_at"])
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
@@ -560,7 +587,9 @@ class ProjectAssetEndpoint(BaseAPIView):
|
||||
# Get the presigned URL
|
||||
storage = S3Storage(request=request)
|
||||
# Generate a presigned URL to share an S3 object
|
||||
presigned_url = storage.generate_presigned_post(object_name=asset_key, file_type=type, file_size=size_limit)
|
||||
presigned_url = storage.generate_presigned_post(
|
||||
object_name=asset_key, file_type=type, file_size=size_limit
|
||||
)
|
||||
# Return the presigned URL
|
||||
return Response(
|
||||
{
|
||||
@@ -590,7 +619,9 @@ class ProjectAssetEndpoint(BaseAPIView):
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def delete(self, request, slug, project_id, pk):
|
||||
# Get the asset
|
||||
asset = FileAsset.objects.get(id=pk, workspace__slug=slug, project_id=project_id)
|
||||
asset = FileAsset.objects.get(
|
||||
id=pk, workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
# Check deleted assets
|
||||
asset.is_deleted = True
|
||||
asset.deleted_at = timezone.now()
|
||||
@@ -601,7 +632,9 @@ class ProjectAssetEndpoint(BaseAPIView):
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def get(self, request, slug, project_id, pk):
|
||||
# get the asset id
|
||||
asset = FileAsset.objects.get(workspace__slug=slug, project_id=project_id, pk=pk)
|
||||
asset = FileAsset.objects.get(
|
||||
workspace__slug=slug, project_id=project_id, pk=pk
|
||||
)
|
||||
|
||||
# Check if the asset is uploaded
|
||||
if not asset.is_uploaded:
|
||||
@@ -634,7 +667,9 @@ class ProjectBulkAssetEndpoint(BaseAPIView):
|
||||
|
||||
# Check if the asset ids are provided
|
||||
if not asset_ids:
|
||||
return Response({"error": "No asset ids provided."}, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(
|
||||
{"error": "No asset ids provided."}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
# get the asset id
|
||||
assets = FileAsset.objects.filter(id__in=asset_ids, workspace__slug=slug)
|
||||
@@ -688,10 +723,110 @@ class AssetCheckEndpoint(BaseAPIView):
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||
def get(self, request, slug, asset_id):
|
||||
asset = FileAsset.all_objects.filter(id=asset_id, workspace__slug=slug, deleted_at__isnull=True).exists()
|
||||
asset = FileAsset.all_objects.filter(
|
||||
id=asset_id, workspace__slug=slug, deleted_at__isnull=True
|
||||
).exists()
|
||||
return Response({"exists": asset}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class DuplicateAssetEndpoint(BaseAPIView):
|
||||
|
||||
throttle_classes = [AssetRateThrottle]
|
||||
|
||||
def get_entity_id_field(self, entity_type, entity_id):
|
||||
# Workspace Logo
|
||||
if entity_type == FileAsset.EntityTypeContext.WORKSPACE_LOGO:
|
||||
return {"workspace_id": entity_id}
|
||||
|
||||
# Project Cover
|
||||
if entity_type == FileAsset.EntityTypeContext.PROJECT_COVER:
|
||||
return {"project_id": entity_id}
|
||||
|
||||
# User Avatar and Cover
|
||||
if entity_type in [
|
||||
FileAsset.EntityTypeContext.USER_AVATAR,
|
||||
FileAsset.EntityTypeContext.USER_COVER,
|
||||
]:
|
||||
return {"user_id": entity_id}
|
||||
|
||||
# Issue Attachment and Description
|
||||
if entity_type in [
|
||||
FileAsset.EntityTypeContext.ISSUE_ATTACHMENT,
|
||||
FileAsset.EntityTypeContext.ISSUE_DESCRIPTION,
|
||||
]:
|
||||
return {"issue_id": entity_id}
|
||||
|
||||
# Page Description
|
||||
if entity_type == FileAsset.EntityTypeContext.PAGE_DESCRIPTION:
|
||||
return {"page_id": entity_id}
|
||||
|
||||
# Comment Description
|
||||
if entity_type == FileAsset.EntityTypeContext.COMMENT_DESCRIPTION:
|
||||
return {"comment_id": entity_id}
|
||||
|
||||
return {}
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
|
||||
def post(self, request, slug, asset_id):
|
||||
project_id = request.data.get("project_id", None)
|
||||
entity_id = request.data.get("entity_id", None)
|
||||
entity_type = request.data.get("entity_type", None)
|
||||
|
||||
if (
|
||||
not entity_id
|
||||
or not entity_type
|
||||
or entity_type not in FileAsset.EntityTypeContext.values
|
||||
):
|
||||
return Response(
|
||||
{"error": "Invalid entity type or entity id"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
workspace = Workspace.objects.get(slug=slug)
|
||||
if project_id:
|
||||
# check if project exists in the workspace
|
||||
if not Project.objects.filter(id=project_id, workspace=workspace).exists():
|
||||
return Response(
|
||||
{"error": "Project not found"}, status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
|
||||
storage = S3Storage(request=request)
|
||||
original_asset = FileAsset.objects.filter(
|
||||
workspace=workspace, id=asset_id, is_uploaded=True
|
||||
).first()
|
||||
|
||||
if not original_asset:
|
||||
return Response(
|
||||
{"error": "Asset not found"}, status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
|
||||
destination_key = (
|
||||
f"{workspace.id}/{uuid.uuid4().hex}-{original_asset.attributes.get('name')}"
|
||||
)
|
||||
duplicated_asset = FileAsset.objects.create(
|
||||
attributes={
|
||||
"name": original_asset.attributes.get("name"),
|
||||
"type": original_asset.attributes.get("type"),
|
||||
"size": original_asset.attributes.get("size"),
|
||||
},
|
||||
asset=destination_key,
|
||||
size=original_asset.size,
|
||||
workspace=workspace,
|
||||
created_by_id=request.user.id,
|
||||
entity_type=entity_type,
|
||||
project_id=project_id if project_id else None,
|
||||
storage_metadata=original_asset.storage_metadata,
|
||||
**self.get_entity_id_field(entity_type=entity_type, entity_id=entity_id),
|
||||
)
|
||||
storage.copy_object(original_asset.asset, destination_key)
|
||||
# Update the is_uploaded field for all newly created assets
|
||||
FileAsset.objects.filter(id=duplicated_asset.id).update(is_uploaded=True)
|
||||
|
||||
return Response(
|
||||
{"asset_id": str(duplicated_asset.id)}, status=status.HTTP_200_OK
|
||||
)
|
||||
|
||||
|
||||
class WorkspaceAssetDownloadEndpoint(BaseAPIView):
|
||||
"""Endpoint to generate a download link for an asset with content-disposition=attachment."""
|
||||
|
||||
|
||||
@@ -410,8 +410,7 @@ def get_webhook_logs_queryset():
|
||||
"response_headers",
|
||||
"retry_count",
|
||||
)
|
||||
.order_by("created_at")
|
||||
.iterator(chunk_size=100)
|
||||
.iterator(chunk_size=BATCH_SIZE)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1154,10 +1154,7 @@ def create_comment_reaction_activity(
|
||||
.values_list("id", "comment__id")
|
||||
.first()
|
||||
)
|
||||
comment = IssueComment.objects.filter(pk=comment_id, project_id=project_id).first()
|
||||
if comment is None:
|
||||
return
|
||||
|
||||
comment = IssueComment.objects.get(pk=comment_id, project_id=project_id)
|
||||
if comment is not None and comment_reaction_id is not None and comment_id is not None:
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
|
||||
@@ -48,8 +48,6 @@ from plane.db.models import (
|
||||
)
|
||||
from plane.license.utils.instance_value import get_email_configuration
|
||||
from plane.utils.exception_logger import log_exception
|
||||
from plane.settings.mongo import MongoConnection
|
||||
|
||||
|
||||
SERIALIZER_MAPPER = {
|
||||
"project": ProjectSerializer,
|
||||
@@ -86,58 +84,6 @@ def get_issue_prefetches():
|
||||
]
|
||||
|
||||
|
||||
|
||||
def save_webhook_log(
|
||||
webhook: Webhook,
|
||||
request_method: str,
|
||||
request_headers: str,
|
||||
request_body: str,
|
||||
response_status: str,
|
||||
response_headers: str,
|
||||
response_body: str,
|
||||
retry_count: int,
|
||||
event_type: str,
|
||||
) -> None:
|
||||
|
||||
# webhook_logs
|
||||
mongo_collection = MongoConnection.get_collection("webhook_logs")
|
||||
|
||||
log_data = {
|
||||
"workspace_id": str(webhook.workspace_id),
|
||||
"webhook": str(webhook.id),
|
||||
"event_type": str(event_type),
|
||||
"request_method": str(request_method),
|
||||
"request_headers": str(request_headers),
|
||||
"request_body": str(request_body),
|
||||
"response_status": str(response_status),
|
||||
"response_headers": str(response_headers),
|
||||
"response_body": str(response_body),
|
||||
"retry_count": retry_count,
|
||||
}
|
||||
|
||||
mongo_save_success = False
|
||||
if mongo_collection is not None:
|
||||
try:
|
||||
# insert the log data into the mongo collection
|
||||
mongo_collection.insert_one(log_data)
|
||||
logger.info("Webhook log saved successfully to mongo")
|
||||
mongo_save_success = True
|
||||
except Exception as e:
|
||||
log_exception(e)
|
||||
logger.error(f"Failed to save webhook log: {e}")
|
||||
mongo_save_success = False
|
||||
|
||||
# if the mongo save is not successful, save the log data into the database
|
||||
if not mongo_save_success:
|
||||
try:
|
||||
# insert the log data into the database
|
||||
WebhookLog.objects.create(**log_data)
|
||||
logger.info("Webhook log saved successfully to database")
|
||||
except Exception as e:
|
||||
log_exception(e)
|
||||
logger.error(f"Failed to save webhook log: {e}")
|
||||
|
||||
|
||||
def get_model_data(event: str, event_id: Union[str, List[str]], many: bool = False) -> Dict[str, Any]:
|
||||
"""
|
||||
Retrieve and serialize model data based on the event type.
|
||||
@@ -327,30 +273,32 @@ def webhook_send_task(
|
||||
response = requests.post(webhook.url, headers=headers, json=payload, timeout=30)
|
||||
|
||||
# Log the webhook request
|
||||
save_webhook_log(
|
||||
webhook=webhook,
|
||||
request_method=action,
|
||||
request_headers=headers,
|
||||
request_body=payload,
|
||||
response_status=response.status_code,
|
||||
response_headers=response.headers,
|
||||
response_body=response.text,
|
||||
retry_count=self.request.retries,
|
||||
event_type=event,
|
||||
WebhookLog.objects.create(
|
||||
workspace_id=str(webhook.workspace_id),
|
||||
webhook=str(webhook.id),
|
||||
event_type=str(event),
|
||||
request_method=str(action),
|
||||
request_headers=str(headers),
|
||||
request_body=str(payload),
|
||||
response_status=str(response.status_code),
|
||||
response_headers=str(response.headers),
|
||||
response_body=str(response.text),
|
||||
retry_count=str(self.request.retries),
|
||||
)
|
||||
logger.info(f"Webhook {webhook.id} sent successfully")
|
||||
except requests.RequestException as e:
|
||||
# Log the failed webhook request
|
||||
save_webhook_log(
|
||||
webhook=webhook,
|
||||
request_method=action,
|
||||
request_headers=headers,
|
||||
request_body=payload,
|
||||
WebhookLog.objects.create(
|
||||
workspace_id=str(webhook.workspace_id),
|
||||
webhook=str(webhook.id),
|
||||
event_type=str(event),
|
||||
request_method=str(action),
|
||||
request_headers=str(headers),
|
||||
request_body=str(payload),
|
||||
response_status=500,
|
||||
response_headers="",
|
||||
response_body=str(e),
|
||||
retry_count=self.request.retries,
|
||||
event_type=event,
|
||||
retry_count=str(self.request.retries),
|
||||
)
|
||||
logger.error(f"Webhook {webhook.id} failed with error: {e}")
|
||||
# Retry logic
|
||||
|
||||
@@ -12,6 +12,7 @@ from rest_framework.request import Request
|
||||
from plane.utils.ip_address import get_client_ip
|
||||
from plane.db.models import APIActivityLog
|
||||
|
||||
|
||||
api_logger = logging.getLogger("plane.api.request")
|
||||
|
||||
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
from django.core.exceptions import RequestDataTooBig
|
||||
from django.http import JsonResponse
|
||||
|
||||
|
||||
class RequestBodySizeLimitMiddleware:
|
||||
"""
|
||||
Middleware to catch RequestDataTooBig exceptions and return
|
||||
413 Request Entity Too Large instead of 400 Bad Request.
|
||||
"""
|
||||
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request):
|
||||
try:
|
||||
_ = request.body
|
||||
except RequestDataTooBig:
|
||||
return JsonResponse(
|
||||
{
|
||||
"error": "REQUEST_BODY_TOO_LARGE",
|
||||
"detail": "The size of the request body exceeds the maximum allowed size.",
|
||||
},
|
||||
status=413,
|
||||
)
|
||||
|
||||
# If body size is OK, continue with the request
|
||||
return self.get_response(request)
|
||||
@@ -62,14 +62,20 @@ MIDDLEWARE = [
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
"crum.CurrentRequestUserMiddleware",
|
||||
"django.middleware.gzip.GZipMiddleware",
|
||||
"plane.middleware.request_body_size.RequestBodySizeLimitMiddleware",
|
||||
"plane.middleware.logger.APITokenLogMiddleware",
|
||||
"plane.middleware.logger.RequestLoggerMiddleware",
|
||||
]
|
||||
|
||||
# Rest Framework settings
|
||||
REST_FRAMEWORK = {
|
||||
"DEFAULT_AUTHENTICATION_CLASSES": ("rest_framework.authentication.SessionAuthentication",),
|
||||
"DEFAULT_AUTHENTICATION_CLASSES": (
|
||||
"rest_framework.authentication.SessionAuthentication",
|
||||
),
|
||||
"DEFAULT_THROTTLE_CLASSES": ("rest_framework.throttling.AnonRateThrottle",),
|
||||
"DEFAULT_THROTTLE_RATES": {
|
||||
"anon": "30/minute",
|
||||
"asset_id": "5/minute",
|
||||
},
|
||||
"DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",),
|
||||
"DEFAULT_RENDERER_CLASSES": ("rest_framework.renderers.JSONRenderer",),
|
||||
"DEFAULT_FILTER_BACKENDS": ("django_filters.rest_framework.DjangoFilterBackend",),
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
from rest_framework.throttling import SimpleRateThrottle
|
||||
|
||||
|
||||
class AssetRateThrottle(SimpleRateThrottle):
|
||||
scope = "asset_id"
|
||||
|
||||
def get_cache_key(self, request, view):
|
||||
asset_id = view.kwargs.get("asset_id")
|
||||
if not asset_id:
|
||||
return None
|
||||
return f"throttle_asset_{asset_id}"
|
||||
@@ -169,17 +169,12 @@ class IssueExportSchema(ExportSchema):
|
||||
def prepare_cycle_start_date(self, i):
|
||||
cycles_dict = self.context.get("cycles_dict") or {}
|
||||
last_cycle = cycles_dict.get(i.id)
|
||||
if last_cycle and last_cycle.cycle.start_date:
|
||||
return self._format_date(last_cycle.cycle.start_date)
|
||||
return ""
|
||||
|
||||
return last_cycle.cycle.start_date if last_cycle else None
|
||||
|
||||
def prepare_cycle_end_date(self, i):
|
||||
cycles_dict = self.context.get("cycles_dict") or {}
|
||||
last_cycle = cycles_dict.get(i.id)
|
||||
if last_cycle and last_cycle.cycle.end_date:
|
||||
return self._format_date(last_cycle.cycle.end_date)
|
||||
return ""
|
||||
return last_cycle.cycle.end_date if last_cycle else None
|
||||
|
||||
def prepare_parent(self, i):
|
||||
if not i.parent:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as dotenv from "@dotenvx/dotenvx";
|
||||
import { z } from "zod";
|
||||
import { logger } from "@plane/logger";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
@@ -27,7 +28,7 @@ const envSchema = z.object({
|
||||
const validateEnv = () => {
|
||||
const result = envSchema.safeParse(process.env);
|
||||
if (!result.success) {
|
||||
console.error("❌ Invalid environment variables:", JSON.stringify(result.error.format(), null, 4));
|
||||
logger.error("❌ Invalid environment variables:", JSON.stringify(result.error.format(), null, 4));
|
||||
process.exit(1);
|
||||
}
|
||||
return result.data;
|
||||
|
||||
@@ -1,18 +1,4 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ["@plane/eslint-config/next.js"],
|
||||
rules: {
|
||||
"no-duplicate-imports": "off",
|
||||
"import/no-duplicates": ["error", { "prefer-inline": false }],
|
||||
"import/consistent-type-specifier-style": ["error", "prefer-top-level"],
|
||||
"@typescript-eslint/no-import-type-side-effects": "error",
|
||||
"@typescript-eslint/consistent-type-imports": [
|
||||
"error",
|
||||
{
|
||||
prefer: "type-imports",
|
||||
fixStyle: "separate-type-imports",
|
||||
disallowTypeAnnotations: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
import type { FC } from "react";
|
||||
import { useState } from "react";
|
||||
import { FC, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// plane imports
|
||||
import { SIDEBAR_WIDTH } from "@plane/constants";
|
||||
|
||||
@@ -6,8 +6,7 @@ import { useRouter } from "next/navigation";
|
||||
// plane package imports
|
||||
import { EUserPermissions, EUserPermissionsLevel, PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Tabs } from "@plane/ui";
|
||||
import type { TabItem } from "@plane/ui";
|
||||
import { type TabItem, Tabs } from "@plane/ui";
|
||||
// components
|
||||
import AnalyticsFilterActions from "@/components/analytics/analytics-filter-actions";
|
||||
import { PageHead } from "@/components/core/page-title";
|
||||
|
||||
@@ -17,7 +17,7 @@ import { SidebarProjectsListItem } from "@/components/workspace/sidebar/projects
|
||||
import { useAppTheme } from "@/hooks/store/use-app-theme";
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
import type { TProject } from "@/plane-web/types";
|
||||
import { TProject } from "@/plane-web/types";
|
||||
import { ExtendedSidebarWrapper } from "./extended-sidebar-wrapper";
|
||||
|
||||
export const ExtendedProjectSidebar = observer(() => {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import type { FC } from "react";
|
||||
import React from "react";
|
||||
import React, { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// plane imports
|
||||
import { EXTENDED_SIDEBAR_WIDTH, SIDEBAR_WIDTH } from "@plane/constants";
|
||||
|
||||
@@ -5,7 +5,7 @@ import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { WORKSPACE_SIDEBAR_DYNAMIC_NAVIGATION_ITEMS_LINKS } from "@plane/constants";
|
||||
import type { EUserWorkspaceRoles } from "@plane/types";
|
||||
import { EUserWorkspaceRoles } from "@plane/types";
|
||||
// hooks
|
||||
import { useAppTheme } from "@/hooks/store/use-app-theme";
|
||||
import { useWorkspace } from "@/hooks/store/use-workspace";
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
"use client";
|
||||
|
||||
// ui
|
||||
import type { FC } from "react";
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { ChevronDown, PanelRight } from "lucide-react";
|
||||
import { PROFILE_VIEWER_TAB, PROFILE_ADMINS_TAB, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { YourWorkIcon } from "@plane/propel/icons";
|
||||
import type { IUserProfileProjectSegregation } from "@plane/types";
|
||||
import { IUserProfileProjectSegregation } from "@plane/types";
|
||||
import { Breadcrumbs, Header, CustomMenu } from "@plane/ui";
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
|
||||
@@ -10,13 +10,13 @@ import { EIssueFilterType, ISSUE_LAYOUTS, ISSUE_DISPLAY_FILTERS_BY_PAGE } from "
|
||||
// plane i18n
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// types
|
||||
import type {
|
||||
import {
|
||||
EIssuesStoreType,
|
||||
IIssueDisplayFilterOptions,
|
||||
IIssueDisplayProperties,
|
||||
TIssueLayouts,
|
||||
EIssueLayoutTypes,
|
||||
} from "@plane/types";
|
||||
import { EIssuesStoreType } from "@plane/types";
|
||||
// ui
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
// components
|
||||
|
||||
@@ -5,7 +5,7 @@ import useSWR from "swr";
|
||||
// plane imports
|
||||
import { GROUP_CHOICES } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import type { IUserStateDistribution, TStateGroups } from "@plane/types";
|
||||
import { IUserStateDistribution, TStateGroups } from "@plane/types";
|
||||
import { ContentWrapper } from "@plane/ui";
|
||||
// components
|
||||
import { PageHead } from "@/components/core/page-title";
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import type { FC } from "react";
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { ArchiveIcon, CycleIcon, ModuleIcon, WorkItemsIcon } from "@plane/propel/icons";
|
||||
|
||||
+7
-2
@@ -19,8 +19,13 @@ import { useTranslation } from "@plane/i18n";
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { CycleIcon } from "@plane/propel/icons";
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
import type { ICustomSearchSelectOption, IIssueDisplayFilterOptions, IIssueDisplayProperties } from "@plane/types";
|
||||
import { EIssuesStoreType, EIssueLayoutTypes } from "@plane/types";
|
||||
import {
|
||||
EIssuesStoreType,
|
||||
ICustomSearchSelectOption,
|
||||
IIssueDisplayFilterOptions,
|
||||
IIssueDisplayProperties,
|
||||
EIssueLayoutTypes,
|
||||
} from "@plane/types";
|
||||
import { Breadcrumbs, BreadcrumbNavigationSearchDropdown, Header } from "@plane/ui";
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
|
||||
+1
-2
@@ -7,8 +7,7 @@ import { Calendar, ChevronDown, Kanban, List } from "lucide-react";
|
||||
// plane imports
|
||||
import { EIssueFilterType, ISSUE_LAYOUTS, ISSUE_DISPLAY_FILTERS_BY_PAGE } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import type { IIssueDisplayFilterOptions, IIssueDisplayProperties, EIssueLayoutTypes } from "@plane/types";
|
||||
import { EIssuesStoreType } from "@plane/types";
|
||||
import { EIssuesStoreType, IIssueDisplayFilterOptions, IIssueDisplayProperties, EIssueLayoutTypes } from "@plane/types";
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
// components
|
||||
import { WorkItemsModal } from "@/components/analytics/work-items/modal";
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import type { FC } from "react";
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// ui
|
||||
|
||||
+2
-3
@@ -2,10 +2,9 @@
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
// ui
|
||||
import { GanttChartSquare, LayoutGrid, List } from "lucide-react";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import { GanttChartSquare, LayoutGrid, List, type LucideIcon } from "lucide-react";
|
||||
// plane package imports
|
||||
import type { TCycleLayoutOptions } from "@plane/types";
|
||||
import { TCycleLayoutOptions } from "@plane/types";
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
// hooks
|
||||
import { useCycleFilter } from "@/hooks/store/use-cycle-filter";
|
||||
|
||||
+1
-2
@@ -6,8 +6,7 @@ import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { EUserPermissionsLevel, CYCLE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import type { TCycleFilters } from "@plane/types";
|
||||
import { EUserProjectRoles } from "@plane/types";
|
||||
import { EUserProjectRoles, TCycleFilters } from "@plane/types";
|
||||
// components
|
||||
import { Header, EHeaderVariant } from "@plane/ui";
|
||||
import { calculateTotalFilters } from "@plane/utils";
|
||||
|
||||
+1
-2
@@ -7,8 +7,7 @@ import { ChevronDown } from "lucide-react";
|
||||
// plane imports
|
||||
import { EIssueFilterType, ISSUE_DISPLAY_FILTERS_BY_PAGE } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import type { IIssueDisplayFilterOptions, IIssueDisplayProperties } from "@plane/types";
|
||||
import { EIssuesStoreType, EIssueLayoutTypes } from "@plane/types";
|
||||
import { EIssuesStoreType, IIssueDisplayFilterOptions, IIssueDisplayProperties, EIssueLayoutTypes } from "@plane/types";
|
||||
// components
|
||||
import { WorkItemsModal } from "@/components/analytics/work-items/modal";
|
||||
import {
|
||||
|
||||
+7
-2
@@ -17,8 +17,13 @@ import {
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { ModuleIcon } from "@plane/propel/icons";
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
import type { ICustomSearchSelectOption, IIssueDisplayFilterOptions, IIssueDisplayProperties } from "@plane/types";
|
||||
import { EIssuesStoreType, EIssueLayoutTypes } from "@plane/types";
|
||||
import {
|
||||
EIssuesStoreType,
|
||||
ICustomSearchSelectOption,
|
||||
IIssueDisplayFilterOptions,
|
||||
IIssueDisplayProperties,
|
||||
EIssueLayoutTypes,
|
||||
} from "@plane/types";
|
||||
import { Breadcrumbs, Header, BreadcrumbNavigationSearchDropdown } from "@plane/ui";
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
|
||||
+1
-2
@@ -8,8 +8,7 @@ import { Calendar, ChevronDown, Kanban, List } from "lucide-react";
|
||||
// plane imports
|
||||
import { EIssueFilterType, ISSUE_LAYOUTS, ISSUE_DISPLAY_FILTERS_BY_PAGE } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import type { IIssueDisplayFilterOptions, IIssueDisplayProperties, EIssueLayoutTypes } from "@plane/types";
|
||||
import { EIssuesStoreType } from "@plane/types";
|
||||
import { EIssuesStoreType, IIssueDisplayFilterOptions, IIssueDisplayProperties, EIssueLayoutTypes } from "@plane/types";
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
// components
|
||||
import { WorkItemsModal } from "@/components/analytics/work-items/modal";
|
||||
|
||||
+1
-2
@@ -6,8 +6,7 @@ import { useParams } from "next/navigation";
|
||||
// types
|
||||
import { EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import type { TModuleFilters } from "@plane/types";
|
||||
import { EUserProjectRoles } from "@plane/types";
|
||||
import { EUserProjectRoles, TModuleFilters } from "@plane/types";
|
||||
// components
|
||||
import { calculateTotalFilters } from "@plane/utils";
|
||||
import { PageHead } from "@/components/core/page-title";
|
||||
|
||||
+14
-6
@@ -7,8 +7,7 @@ import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
// plane types
|
||||
import { getButtonStyling } from "@plane/propel/button";
|
||||
import type { TSearchEntityRequestPayload, TWebhookConnectionQueryParams } from "@plane/types";
|
||||
import { EFileAssetType } from "@plane/types";
|
||||
import { EFileAssetType, TSearchEntityRequestPayload, TWebhookConnectionQueryParams } from "@plane/types";
|
||||
// plane ui
|
||||
// plane utils
|
||||
import { cn } from "@plane/utils";
|
||||
@@ -16,8 +15,7 @@ import { cn } from "@plane/utils";
|
||||
import { LogoSpinner } from "@/components/common/logo-spinner";
|
||||
import { PageHead } from "@/components/core/page-title";
|
||||
import { IssuePeekOverview } from "@/components/issues/peek-overview";
|
||||
import type { TPageRootConfig, TPageRootHandlers } from "@/components/pages/editor/page-root";
|
||||
import { PageRoot } from "@/components/pages/editor/page-root";
|
||||
import { PageRoot, TPageRootConfig, TPageRootHandlers } from "@/components/pages/editor/page-root";
|
||||
// hooks
|
||||
import { useEditorConfig } from "@/hooks/editor";
|
||||
import { useEditorAsset } from "@/hooks/store/use-editor-asset";
|
||||
@@ -46,7 +44,7 @@ const PageDetailsPage = observer(() => {
|
||||
storeType,
|
||||
});
|
||||
const { getWorkspaceBySlug } = useWorkspace();
|
||||
const { uploadEditorAsset } = useEditorAsset();
|
||||
const { uploadEditorAsset, duplicateEditorAsset } = useEditorAsset();
|
||||
// derived values
|
||||
const workspaceId = workspaceSlug ? (getWorkspaceBySlug(workspaceSlug.toString())?.id ?? "") : "";
|
||||
const { canCurrentUserAccessPage, id, name, updateDescription } = page ?? {};
|
||||
@@ -133,11 +131,21 @@ const PageDetailsPage = observer(() => {
|
||||
});
|
||||
return asset_id;
|
||||
},
|
||||
duplicateFile: async (assetId: string) => {
|
||||
const { asset_id } = await duplicateEditorAsset({
|
||||
assetId,
|
||||
entityId: id ?? "",
|
||||
entityType: EFileAssetType.PAGE_DESCRIPTION,
|
||||
projectId: projectId?.toString() ?? "",
|
||||
workspaceSlug: workspaceSlug?.toString() ?? "",
|
||||
});
|
||||
return asset_id;
|
||||
},
|
||||
workspaceId,
|
||||
workspaceSlug: workspaceSlug?.toString() ?? "",
|
||||
}),
|
||||
}),
|
||||
[getEditorFileHandlers, id, uploadEditorAsset, projectId, workspaceId, workspaceSlug]
|
||||
[getEditorFileHandlers, projectId, workspaceId, workspaceSlug, uploadEditorAsset, id, duplicateEditorAsset]
|
||||
);
|
||||
|
||||
const webhookConnectionParams: TWebhookConnectionQueryParams = useMemo(
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ import { useParams } from "next/navigation";
|
||||
import { EProjectFeatureKey } from "@plane/constants";
|
||||
import { PageIcon } from "@plane/propel/icons";
|
||||
// types
|
||||
import type { ICustomSearchSelectOption } from "@plane/types";
|
||||
import { ICustomSearchSelectOption } from "@plane/types";
|
||||
// ui
|
||||
import { Breadcrumbs, Header, BreadcrumbNavigationSearchDropdown } from "@plane/ui";
|
||||
// components
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ import {
|
||||
// plane types
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import type { TPage } from "@plane/types";
|
||||
import { TPage } from "@plane/types";
|
||||
// plane ui
|
||||
import { Breadcrumbs, Header } from "@plane/ui";
|
||||
// helpers
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
import { ReactNode } from "react";
|
||||
// components
|
||||
import { AppHeader } from "@/components/core/app-header";
|
||||
import { ContentWrapper } from "@/components/core/content-wrapper";
|
||||
|
||||
+1
-2
@@ -5,8 +5,7 @@ import { useParams, useSearchParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import type { TPageNavigationTabs } from "@plane/types";
|
||||
import { EUserProjectRoles } from "@plane/types";
|
||||
import { EUserProjectRoles, TPageNavigationTabs } from "@plane/types";
|
||||
// components
|
||||
import { PageHead } from "@/components/core/page-title";
|
||||
import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root";
|
||||
|
||||
+8
-2
@@ -17,8 +17,14 @@ import {
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { ViewsIcon } from "@plane/propel/icons";
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
import type { ICustomSearchSelectOption, IIssueDisplayFilterOptions, IIssueDisplayProperties } from "@plane/types";
|
||||
import { EIssuesStoreType, EViewAccess, EIssueLayoutTypes } from "@plane/types";
|
||||
import {
|
||||
EIssuesStoreType,
|
||||
EViewAccess,
|
||||
ICustomSearchSelectOption,
|
||||
IIssueDisplayFilterOptions,
|
||||
IIssueDisplayProperties,
|
||||
EIssueLayoutTypes,
|
||||
} from "@plane/types";
|
||||
// ui
|
||||
import { Breadcrumbs, Header, BreadcrumbNavigationSearchDropdown } from "@plane/ui";
|
||||
// components
|
||||
|
||||
+1
-2
@@ -6,8 +6,7 @@ import { useParams } from "next/navigation";
|
||||
// components
|
||||
import { EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import type { EViewAccess, TViewFilterProps } from "@plane/types";
|
||||
import { EUserProjectRoles } from "@plane/types";
|
||||
import { EUserProjectRoles, EViewAccess, TViewFilterProps } from "@plane/types";
|
||||
import { Header, EHeaderVariant } from "@plane/ui";
|
||||
import { calculateTotalFilters } from "@plane/utils";
|
||||
import { PageHead } from "@/components/core/page-title";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
import { ReactNode } from "react";
|
||||
// components
|
||||
import { AppHeader } from "@/components/core/app-header";
|
||||
import { ContentWrapper } from "@/components/core/content-wrapper";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
import { ReactNode } from "react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane web layouts
|
||||
import { ProjectAuthWrapper } from "@/plane-web/layouts/project-wrapper";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
import { ReactNode } from "react";
|
||||
// components
|
||||
import { AppHeader } from "@/components/core/app-header";
|
||||
import { ContentWrapper } from "@/components/core/content-wrapper";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { FC } from "react";
|
||||
import { FC } from "react";
|
||||
import { isEmpty } from "lodash-es";
|
||||
import { observer } from "mobx-react";
|
||||
// plane helpers
|
||||
|
||||
@@ -13,8 +13,13 @@ import {
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { ViewsIcon } from "@plane/propel/icons";
|
||||
import type { IIssueDisplayFilterOptions, IIssueDisplayProperties, ICustomSearchSelectOption } from "@plane/types";
|
||||
import { EIssuesStoreType, EIssueLayoutTypes } from "@plane/types";
|
||||
import {
|
||||
EIssuesStoreType,
|
||||
IIssueDisplayFilterOptions,
|
||||
IIssueDisplayProperties,
|
||||
ICustomSearchSelectOption,
|
||||
EIssueLayoutTypes,
|
||||
} from "@plane/types";
|
||||
import { Breadcrumbs, Header, BreadcrumbNavigationSearchDropdown } from "@plane/ui";
|
||||
// components
|
||||
import { BreadcrumbLink } from "@/components/common/breadcrumb-link";
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import type { FC, ReactNode } from "react";
|
||||
import { FC, ReactNode } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { usePathname } from "next/navigation";
|
||||
// constants
|
||||
import { WORKSPACE_SETTINGS_ACCESS } from "@plane/constants";
|
||||
import type { EUserWorkspaceRoles } from "@plane/types";
|
||||
import { EUserWorkspaceRoles } from "@plane/types";
|
||||
// components
|
||||
import { NotAuthorizedView } from "@/components/auth-screens/not-authorized-view";
|
||||
import { getWorkspaceActivePath, pathnameToAccessKey } from "@/components/settings/helper";
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import type { IWorkspaceBulkInviteFormData } from "@plane/types";
|
||||
import { IWorkspaceBulkInviteFormData } from "@plane/types";
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
import { NotAuthorizedView } from "@/components/auth-screens/not-authorized-view";
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
EUserPermissions,
|
||||
WORKSPACE_SETTINGS_CATEGORY,
|
||||
} from "@plane/constants";
|
||||
import type { EUserWorkspaceRoles } from "@plane/types";
|
||||
import { EUserWorkspaceRoles } from "@plane/types";
|
||||
import { SettingsSidebar } from "@/components/settings/sidebar";
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
import { shouldRenderSettingLink } from "@/plane-web/helpers/workspace.helper";
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
import { EUserPermissions, EUserPermissionsLevel, WORKSPACE_SETTINGS_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import type { IWebhook } from "@plane/types";
|
||||
import { IWebhook } from "@plane/types";
|
||||
// ui
|
||||
// components
|
||||
import { LogoSpinner } from "@/components/common/logo-spinner";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
import { ReactNode } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { usePathname } from "next/navigation";
|
||||
// components
|
||||
|
||||
@@ -15,8 +15,7 @@ import { getPasswordStrength } from "@plane/utils";
|
||||
import { PageHead } from "@/components/core/page-title";
|
||||
import { ProfileSettingContentHeader } from "@/components/profile/profile-setting-content-header";
|
||||
// helpers
|
||||
import { authErrorHandler } from "@/helpers/authentication.helper";
|
||||
import type { EAuthenticationErrorCodes } from "@/helpers/authentication.helper";
|
||||
import { authErrorHandler, type EAuthenticationErrorCodes } from "@/helpers/authentication.helper";
|
||||
// hooks
|
||||
import { useUser } from "@/hooks/store/user";
|
||||
// services
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ import { useParams } from "next/navigation";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import type { IProject } from "@plane/types";
|
||||
import { IProject } from "@plane/types";
|
||||
// ui
|
||||
// components
|
||||
import { NotAuthorizedView } from "@/components/auth-screens/not-authorized-view";
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { ReactNode, useEffect } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
// components
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Forgot Password - Plane",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Reset Password - Plane",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Set Password - Plane",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Workspace",
|
||||
|
||||
@@ -8,7 +8,7 @@ import Link from "next/link";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button, getButtonStyling } from "@plane/propel/button";
|
||||
import { PlaneLogo } from "@plane/propel/icons";
|
||||
import type { IWorkspace } from "@plane/types";
|
||||
import { IWorkspace } from "@plane/types";
|
||||
// components
|
||||
import { CreateWorkspaceForm } from "@/components/workspace/create-workspace-form";
|
||||
// hooks
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Invitations",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Metadata, Viewport } from "next";
|
||||
import { Metadata, Viewport } from "next";
|
||||
|
||||
import { PreloadResources } from "./layout.preload";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Onboarding",
|
||||
|
||||
@@ -4,11 +4,10 @@ import { useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useTheme } from "next-themes";
|
||||
// plane imports
|
||||
import type { I_THEME_OPTION } from "@plane/constants";
|
||||
import { THEME_OPTIONS } from "@plane/constants";
|
||||
import { I_THEME_OPTION, THEME_OPTIONS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { setPromiseToast } from "@plane/propel/toast";
|
||||
import type { IUserTheme } from "@plane/types";
|
||||
import { IUserTheme } from "@plane/types";
|
||||
// components
|
||||
import { applyTheme, unsetCustomCssVariables } from "@plane/utils";
|
||||
import { LogoSpinner } from "@/components/common/logo-spinner";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
import { ReactNode } from "react";
|
||||
// components
|
||||
import { CommandPalette } from "@/components/command-palette";
|
||||
// wrappers
|
||||
|
||||
@@ -16,8 +16,7 @@ import { PageHead } from "@/components/core/page-title";
|
||||
import { ProfileSettingContentHeader } from "@/components/profile/profile-setting-content-header";
|
||||
import { ProfileSettingContentWrapper } from "@/components/profile/profile-setting-content-wrapper";
|
||||
// helpers
|
||||
import { authErrorHandler } from "@/helpers/authentication.helper";
|
||||
import type { EAuthenticationErrorCodes } from "@/helpers/authentication.helper";
|
||||
import { authErrorHandler, type EAuthenticationErrorCodes } from "@/helpers/authentication.helper";
|
||||
// hooks
|
||||
import { useUser } from "@/hooks/store/user";
|
||||
// services
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Sign up - Plane",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Workspace Invitations",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Metadata, Viewport } from "next";
|
||||
import { Metadata, Viewport } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
robots: {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Metadata, Viewport } from "next";
|
||||
import { Metadata, Viewport } from "next";
|
||||
import Script from "next/script";
|
||||
|
||||
// styles
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import type { Metadata } from "next";
|
||||
import { Metadata } from "next";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
// ui
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import type { FC, ReactNode } from "react";
|
||||
import { FC, ReactNode } from "react";
|
||||
import { AppProgressProvider as ProgressProvider } from "@bprogress/next";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useTheme, ThemeProvider } from "next-themes";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { AnalyticsTab } from "@plane/types";
|
||||
import { AnalyticsTab } from "@plane/types";
|
||||
import { Overview } from "@/components/analytics/overview";
|
||||
import { WorkItems } from "@/components/analytics/work-items";
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import type { FC } from "react";
|
||||
import React from "react";
|
||||
import React, { FC } from "react";
|
||||
|
||||
export type TCustomAutomationsRootProps = {
|
||||
projectId: string;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import type { FC } from "react";
|
||||
import { FC } from "react";
|
||||
// plane imports
|
||||
import type { EProjectFeatureKey } from "@plane/constants";
|
||||
import { EProjectFeatureKey } from "@plane/constants";
|
||||
// local components
|
||||
import { ProjectBreadcrumb } from "./project";
|
||||
import { ProjectFeatureBreadcrumb } from "./project-feature";
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import type { FC } from "react";
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// plane imports
|
||||
import { EProjectFeatureKey } from "@plane/constants";
|
||||
import type { ISvgIcons } from "@plane/propel/icons";
|
||||
import { ISvgIcons } from "@plane/propel/icons";
|
||||
import { BreadcrumbNavigationDropdown, Breadcrumbs } from "@plane/ui";
|
||||
// components
|
||||
import { SwitcherLabel } from "@/components/common/switcher-label";
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { observer } from "mobx-react";
|
||||
import { ProjectIcon } from "@plane/propel/icons";
|
||||
// plane imports
|
||||
import type { ICustomSearchSelectOption } from "@plane/types";
|
||||
import { ICustomSearchSelectOption } from "@plane/types";
|
||||
import { BreadcrumbNavigationSearchDropdown, Breadcrumbs } from "@plane/ui";
|
||||
// components
|
||||
import { Logo } from "@/components/common/logo";
|
||||
@@ -11,7 +11,7 @@ import { SwitcherLabel } from "@/components/common/switcher-label";
|
||||
// hooks
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import type { TProject } from "@/plane-web/types";
|
||||
import { TProject } from "@/plane-web/types";
|
||||
|
||||
type TProjectBreadcrumbProps = {
|
||||
workspaceSlug: string;
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
"use client";
|
||||
|
||||
// types
|
||||
import { LayoutGrid } from "lucide-react";
|
||||
// plane imports
|
||||
import { CycleIcon, ModuleIcon, PageIcon, ProjectIcon, ViewsIcon } from "@plane/propel/icons";
|
||||
import type {
|
||||
import {
|
||||
IWorkspaceDefaultSearchResult,
|
||||
IWorkspaceIssueSearchResult,
|
||||
IWorkspacePageSearchResult,
|
||||
IWorkspaceProjectSearchResult,
|
||||
IWorkspaceSearchResult,
|
||||
} from "@plane/types";
|
||||
// ui
|
||||
// helpers
|
||||
import { generateWorkItemLink } from "@plane/utils";
|
||||
// plane web components
|
||||
import { IssueIdentifier } from "@/plane-web/components/issues/issue-details/issue-identifier";
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import type { FC } from "react";
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import type { TIssue } from "@plane/types";
|
||||
import { EIssueServiceType, EIssuesStoreType } from "@plane/types";
|
||||
import { EIssueServiceType, EIssuesStoreType, TIssue } from "@plane/types";
|
||||
// components
|
||||
import { BulkDeleteIssuesModal } from "@/components/core/modals/bulk-delete-issues-modal";
|
||||
import { DeleteIssueModal } from "@/components/issues/delete-issue-modal";
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import type { FC, ReactNode } from "react";
|
||||
import { useRef } from "react";
|
||||
import { FC, ReactNode, useRef } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// plane imports
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import type { TIssueComment } from "@plane/types";
|
||||
import { EIssueCommentAccessSpecifier } from "@plane/types";
|
||||
import { EIssueCommentAccessSpecifier, TIssueComment } from "@plane/types";
|
||||
import { Avatar, Tooltip } from "@plane/ui";
|
||||
import { calculateTimeAgo, cn, getFileURL, renderFormattedDate, renderFormattedTime } from "@plane/utils";
|
||||
// hooks
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { ReactNode } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { AppSidebarToggleButton } from "@/components/sidebar/sidebar-toggle-button";
|
||||
import { useAppTheme } from "@/hooks/store/use-app-theme";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { IWorkspace } from "@plane/types";
|
||||
import { IWorkspace } from "@plane/types";
|
||||
|
||||
type TProps = {
|
||||
workspace?: IWorkspace;
|
||||
|
||||
@@ -17,7 +17,7 @@ import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-stat
|
||||
// hooks
|
||||
import { useCycle } from "@/hooks/store/use-cycle";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
import type { ActiveCycleIssueDetails } from "@/store/issue/cycle";
|
||||
import { ActiveCycleIssueDetails } from "@/store/issue/cycle";
|
||||
|
||||
interface IActiveCycleDetails {
|
||||
workspaceSlug: string;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { FC } from "react";
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
type Props = {
|
||||
cycleId: string;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
"use client";
|
||||
import type { FC } from "react";
|
||||
import { Fragment } from "react";
|
||||
import { FC, Fragment } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// plane imports
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import type { TCycleEstimateType } from "@plane/types";
|
||||
import { TCycleEstimateType } from "@plane/types";
|
||||
import { Loader } from "@plane/ui";
|
||||
import { getDate } from "@plane/utils";
|
||||
// components
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
import type { FC } from "react";
|
||||
import React from "react";
|
||||
import React, { FC } from "react";
|
||||
// components
|
||||
import { SidebarChart } from "./base";
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
import type { FC } from "react";
|
||||
import React from "react";
|
||||
import React, { FC } from "react";
|
||||
// local components
|
||||
|
||||
type TDeDupeButtonRoot = {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"use-client";
|
||||
|
||||
import type { FC } from "react";
|
||||
import { FC } from "react";
|
||||
// types
|
||||
import type { TDeDupeIssue } from "@plane/types";
|
||||
import { TDeDupeIssue } from "@plane/types";
|
||||
|
||||
type TDuplicateModalRootProps = {
|
||||
workspaceSlug: string;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import type { FC } from "react";
|
||||
import React from "react";
|
||||
import React, { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// types
|
||||
import type { TDeDupeIssue } from "@plane/types";
|
||||
import { TDeDupeIssue } from "@plane/types";
|
||||
import type { TIssueOperations } from "@/components/issues/issue-detail";
|
||||
|
||||
type TDeDupeIssuePopoverRootProps = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import type { FC } from "react";
|
||||
import { FC } from "react";
|
||||
|
||||
type TDeDupeIssueButtonLabelProps = {
|
||||
isOpen: boolean;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
import type { FC } from "react";
|
||||
import React from "react";
|
||||
import type { TIssue } from "@plane/types";
|
||||
import React, { FC } from "react";
|
||||
import { TIssue } from "@plane/types";
|
||||
|
||||
export interface EpicModalProps {
|
||||
data?: Partial<TIssue>;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { FC } from "react";
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Pen, Trash } from "lucide-react";
|
||||
import { PROJECT_SETTINGS_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { TEstimateSystemKeys } from "@plane/types";
|
||||
import { EEstimateSystem } from "@plane/types";
|
||||
import { TEstimateSystemKeys, EEstimateSystem } from "@plane/types";
|
||||
|
||||
export const isEstimateSystemEnabled = (key: TEstimateSystemKeys) => {
|
||||
switch (key) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { FC } from "react";
|
||||
import { FC } from "react";
|
||||
|
||||
export type TEstimateTimeInputProps = {
|
||||
value?: number;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import type { FC } from "react";
|
||||
import { FC } from "react";
|
||||
|
||||
import type { TEstimatePointsObject, TEstimateSystemKeys, TEstimateTypeErrorObject } from "@plane/types";
|
||||
import { TEstimatePointsObject, TEstimateSystemKeys, TEstimateTypeErrorObject } from "@plane/types";
|
||||
|
||||
export type TEstimatePointDelete = {
|
||||
workspaceSlug: string;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import type { FC } from "react";
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
|
||||
type TUpdateEstimateModal = {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { FC } from "react";
|
||||
import { FC } from "react";
|
||||
// components
|
||||
import type { IBlockUpdateData, IGanttBlock } from "@plane/types";
|
||||
import RenderIfVisible from "@/components/core/render-if-visible-HOC";
|
||||
// hooks
|
||||
import { BlockRow } from "@/components/gantt-chart/blocks/block-row";
|
||||
import { BLOCK_HEIGHT } from "@/components/gantt-chart/constants";
|
||||
import type { TSelectionHelper } from "@/hooks/use-multiple-select";
|
||||
import { TSelectionHelper } from "@/hooks/use-multiple-select";
|
||||
// types
|
||||
|
||||
export type GanttChartBlocksProps = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { FC } from "react";
|
||||
import { FC } from "react";
|
||||
//
|
||||
import type { IBlockUpdateDependencyData } from "@plane/types";
|
||||
import { GanttChartBlock } from "@/components/gantt-chart/blocks/block";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { RefObject } from "react";
|
||||
import { RefObject } from "react";
|
||||
import type { IGanttBlock } from "@plane/types";
|
||||
|
||||
type LeftDependencyDraggableProps = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { RefObject } from "react";
|
||||
import { RefObject } from "react";
|
||||
import type { IGanttBlock } from "@plane/types";
|
||||
|
||||
type RightDependencyDraggableProps = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { FC } from "react";
|
||||
import { FC } from "react";
|
||||
|
||||
type Props = {
|
||||
isEpic?: boolean;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { EInboxIssueSource } from "@plane/types";
|
||||
import { EInboxIssueSource } from "@plane/types";
|
||||
|
||||
export type TInboxSourcePill = {
|
||||
source: EInboxIssueSource;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { observer } from "mobx-react";
|
||||
import { BulkOperationsUpgradeBanner } from "@/components/issues/bulk-operations/upgrade-banner";
|
||||
// hooks
|
||||
import { useMultipleSelectStore } from "@/hooks/store/use-multiple-select-store";
|
||||
import type { TSelectionHelper } from "@/hooks/use-multiple-select";
|
||||
import { TSelectionHelper } from "@/hooks/use-multiple-select";
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import type React from "react";
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
|
||||
type Props = {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user