mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
Merge branch 'refactor/mobx-store' of github.com:makeplane/plane into refactor/mobx-store
This commit is contained in:
commit
ccfe2e4b01
@ -33,8 +33,8 @@ The backend is a django project which is kept inside apiserver
|
||||
1. Clone the repo
|
||||
|
||||
```bash
|
||||
git clone https://github.com/makeplane/plane
|
||||
cd plane
|
||||
git clone https://github.com/makeplane/plane.git [folder-name]
|
||||
cd [folder-name]
|
||||
chmod +x setup.sh
|
||||
```
|
||||
|
||||
@ -44,33 +44,12 @@ chmod +x setup.sh
|
||||
./setup.sh
|
||||
```
|
||||
|
||||
3. Define `NEXT_PUBLIC_API_BASE_URL=http://localhost` in **web/.env** and **space/.env** file
|
||||
3. Start the containers
|
||||
|
||||
```bash
|
||||
echo "\nNEXT_PUBLIC_API_BASE_URL=http://localhost\n" >> ./web/.env
|
||||
docker compose -f docker-compose-local.yml up
|
||||
```
|
||||
|
||||
```bash
|
||||
echo "\nNEXT_PUBLIC_API_BASE_URL=http://localhost\n" >> ./space/.env
|
||||
```
|
||||
|
||||
4. Run Docker compose up
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
5. Install dependencies
|
||||
|
||||
```bash
|
||||
yarn install
|
||||
```
|
||||
|
||||
6. Run the web app in development mode
|
||||
|
||||
```bash
|
||||
yarn dev
|
||||
```
|
||||
|
||||
## Missing a Feature?
|
||||
|
||||
|
@ -37,7 +37,7 @@ Meet [Plane](https://plane.so). An open-source software development tool to mana
|
||||
|
||||
> Plane is still in its early days, not everything will be perfect yet, and hiccups may happen. Please let us know of any suggestions, ideas, or bugs that you encounter on our [Discord](https://discord.com/invite/A92xrEGCge) or GitHub issues, and we will use your feedback to improve on our upcoming releases.
|
||||
|
||||
The easiest way to get started with Plane is by creating a [Plane Cloud](https://app.plane.so) account. Plane Cloud offers a hosted solution for Plane. If you prefer to self-host Plane, please refer to our [deployment documentation](https://docs.plane.so/self-hosting).
|
||||
The easiest way to get started with Plane is by creating a [Plane Cloud](https://app.plane.so) account. Plane Cloud offers a hosted solution for Plane. If you prefer to self-host Plane, please refer to our [deployment documentation](https://docs.plane.so/self-hosting/docker-compose).
|
||||
|
||||
## ⚡️ Contributors Quick Start
|
||||
|
||||
@ -63,7 +63,7 @@ Thats it!
|
||||
|
||||
## 🍙 Self Hosting
|
||||
|
||||
For self hosting environment setup, visit the [Self Hosting](https://docs.plane.so/self-hosting) documentation page
|
||||
For self hosting environment setup, visit the [Self Hosting](https://docs.plane.so/self-hosting/docker-compose) documentation page
|
||||
|
||||
## 🚀 Features
|
||||
|
||||
|
@ -49,5 +49,5 @@ USER captain
|
||||
# Expose container port and run entry point script
|
||||
EXPOSE 8000
|
||||
|
||||
# CMD [ "./bin/takeoff" ]
|
||||
CMD [ "./bin/takeoff.local" ]
|
||||
|
||||
|
31
apiserver/bin/takeoff.local
Executable file
31
apiserver/bin/takeoff.local
Executable file
@ -0,0 +1,31 @@
|
||||
#!/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 register_instance $MACHINE_SIGNATURE
|
||||
# Load the configuration variable
|
||||
python manage.py configure_instance
|
||||
|
||||
# Create the default bucket
|
||||
python manage.py create_bucket
|
||||
|
||||
python manage.py runserver 0.0.0.0:8000 --settings=plane.settings.local
|
||||
|
@ -49,7 +49,6 @@ class IssueStateInboxSerializer(BaseSerializer):
|
||||
label_details = LabelLiteSerializer(read_only=True, source="labels", many=True)
|
||||
assignee_details = UserLiteSerializer(read_only=True, source="assignees", many=True)
|
||||
sub_issues_count = serializers.IntegerField(read_only=True)
|
||||
bridge_id = serializers.UUIDField(read_only=True)
|
||||
issue_inbox = InboxIssueLiteSerializer(read_only=True, many=True)
|
||||
|
||||
class Meta:
|
||||
|
@ -512,7 +512,6 @@ class IssueStateSerializer(DynamicBaseSerializer):
|
||||
project_detail = ProjectLiteSerializer(read_only=True, source="project")
|
||||
assignee_details = UserLiteSerializer(read_only=True, source="assignees", many=True)
|
||||
sub_issues_count = serializers.IntegerField(read_only=True)
|
||||
bridge_id = serializers.UUIDField(read_only=True)
|
||||
attachment_count = serializers.IntegerField(read_only=True)
|
||||
link_count = serializers.IntegerField(read_only=True)
|
||||
|
||||
|
@ -44,7 +44,7 @@ urlpatterns = [
|
||||
name="project-issue-cycle",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/cycles/<uuid:cycle_id>/cycle-issues/<uuid:pk>/",
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/cycles/<uuid:cycle_id>/cycle-issues/<uuid:issue_id>/",
|
||||
CycleIssueViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
|
@ -40,7 +40,7 @@ urlpatterns = [
|
||||
name="inbox-issue",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/inboxes/<uuid:inbox_id>/inbox-issues/<uuid:pk>/",
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/inboxes/<uuid:inbox_id>/inbox-issues/<uuid:issue_id>/",
|
||||
InboxIssueViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
|
@ -44,7 +44,7 @@ urlpatterns = [
|
||||
name="project-module-issues",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/modules/<uuid:module_id>/module-issues/<uuid:pk>/",
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/modules/<uuid:module_id>/module-issues/<uuid:issue_id>/",
|
||||
ModuleIssueViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
|
@ -516,7 +516,6 @@ class CycleIssueViewSet(WebhookMixin, BaseViewSet):
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
.annotate(bridge_id=F("issue_cycle__id"))
|
||||
.filter(project_id=project_id)
|
||||
.filter(workspace__slug=slug)
|
||||
.select_related("project")
|
||||
@ -636,11 +635,10 @@ class CycleIssueViewSet(WebhookMixin, BaseViewSet):
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
def destroy(self, request, slug, project_id, cycle_id, pk):
|
||||
def destroy(self, request, slug, project_id, cycle_id, issue_id):
|
||||
cycle_issue = CycleIssue.objects.get(
|
||||
pk=pk, workspace__slug=slug, project_id=project_id, cycle_id=cycle_id
|
||||
issue_id=issue_id, workspace__slug=slug, project_id=project_id, cycle_id=cycle_id
|
||||
)
|
||||
issue_id = cycle_issue.issue_id
|
||||
issue_activity.delay(
|
||||
type="cycle.activity.deleted",
|
||||
requested_data=json.dumps(
|
||||
@ -650,7 +648,7 @@ class CycleIssueViewSet(WebhookMixin, BaseViewSet):
|
||||
}
|
||||
),
|
||||
actor_id=str(self.request.user.id),
|
||||
issue_id=str(cycle_issue.issue_id),
|
||||
issue_id=str(issue_id),
|
||||
project_id=str(self.kwargs.get("project_id", None)),
|
||||
current_instance=None,
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
|
@ -107,7 +107,6 @@ class InboxIssueViewSet(BaseViewSet):
|
||||
project_id=project_id,
|
||||
)
|
||||
.filter(**filters)
|
||||
.annotate(bridge_id=F("issue_inbox__id"))
|
||||
.select_related("workspace", "project", "state", "parent")
|
||||
.prefetch_related("assignees", "labels")
|
||||
.order_by("issue_inbox__snoozed_till", "issue_inbox__status")
|
||||
@ -204,9 +203,9 @@ class InboxIssueViewSet(BaseViewSet):
|
||||
serializer = IssueStateInboxSerializer(issue)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
def partial_update(self, request, slug, project_id, inbox_id, pk):
|
||||
def partial_update(self, request, slug, project_id, inbox_id, issue_id):
|
||||
inbox_issue = InboxIssue.objects.get(
|
||||
pk=pk, workspace__slug=slug, project_id=project_id, inbox_id=inbox_id
|
||||
issue_id=issue_id, workspace__slug=slug, project_id=project_id, inbox_id=inbox_id
|
||||
)
|
||||
# Get the project member
|
||||
project_member = ProjectMember.objects.get(
|
||||
@ -316,19 +315,16 @@ class InboxIssueViewSet(BaseViewSet):
|
||||
InboxIssueSerializer(inbox_issue).data, status=status.HTTP_200_OK
|
||||
)
|
||||
|
||||
def retrieve(self, request, slug, project_id, inbox_id, pk):
|
||||
inbox_issue = InboxIssue.objects.get(
|
||||
pk=pk, workspace__slug=slug, project_id=project_id, inbox_id=inbox_id
|
||||
)
|
||||
def retrieve(self, request, slug, project_id, inbox_id, issue_id):
|
||||
issue = Issue.objects.get(
|
||||
pk=inbox_issue.issue_id, workspace__slug=slug, project_id=project_id
|
||||
pk=issue_id, workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
serializer = IssueStateInboxSerializer(issue)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
def destroy(self, request, slug, project_id, inbox_id, pk):
|
||||
def destroy(self, request, slug, project_id, inbox_id, issue_id):
|
||||
inbox_issue = InboxIssue.objects.get(
|
||||
pk=pk, workspace__slug=slug, project_id=project_id, inbox_id=inbox_id
|
||||
issue_id=issue_id, workspace__slug=slug, project_id=project_id, inbox_id=inbox_id
|
||||
)
|
||||
# Get the project member
|
||||
project_member = ProjectMember.objects.get(
|
||||
@ -350,7 +346,7 @@ class InboxIssueViewSet(BaseViewSet):
|
||||
if inbox_issue.status in [-2, -1, 0, 2]:
|
||||
# Delete the issue also
|
||||
Issue.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id, pk=inbox_issue.issue_id
|
||||
workspace__slug=slug, project_id=project_id, pk=issue_id
|
||||
).delete()
|
||||
|
||||
inbox_issue.delete()
|
||||
|
@ -342,7 +342,6 @@ class ModuleIssueViewSet(WebhookMixin, BaseViewSet):
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
.annotate(bridge_id=F("issue_module__id"))
|
||||
.filter(project_id=project_id)
|
||||
.filter(workspace__slug=slug)
|
||||
.select_related("project")
|
||||
@ -451,20 +450,20 @@ class ModuleIssueViewSet(WebhookMixin, BaseViewSet):
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
def destroy(self, request, slug, project_id, module_id, pk):
|
||||
def destroy(self, request, slug, project_id, module_id, issue_id):
|
||||
module_issue = ModuleIssue.objects.get(
|
||||
workspace__slug=slug, project_id=project_id, module_id=module_id, pk=pk
|
||||
workspace__slug=slug, project_id=project_id, module_id=module_id, issue_id=issue_id
|
||||
)
|
||||
issue_activity.delay(
|
||||
type="module.activity.deleted",
|
||||
requested_data=json.dumps(
|
||||
{
|
||||
"module_id": str(module_id),
|
||||
"issues": [str(module_issue.issue_id)],
|
||||
"issues": [str(issue_id)],
|
||||
}
|
||||
),
|
||||
actor_id=str(request.user.id),
|
||||
issue_id=str(module_issue.issue_id),
|
||||
issue_id=str(issue_id),
|
||||
project_id=str(project_id),
|
||||
current_instance=None,
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
|
@ -145,6 +145,16 @@ class ProjectViewSet(WebhookMixin, BaseViewSet):
|
||||
)
|
||||
)
|
||||
)
|
||||
.prefetch_related(
|
||||
Prefetch(
|
||||
"project_projectmember",
|
||||
queryset=ProjectMember.objects.filter(
|
||||
workspace__slug=self.kwargs.get("slug"),
|
||||
is_active=True,
|
||||
).select_related("member"),
|
||||
to_attr="members_list",
|
||||
)
|
||||
)
|
||||
.distinct()
|
||||
)
|
||||
|
||||
@ -160,16 +170,6 @@ class ProjectViewSet(WebhookMixin, BaseViewSet):
|
||||
projects = (
|
||||
self.get_queryset()
|
||||
.annotate(sort_order=Subquery(sort_order_query))
|
||||
.prefetch_related(
|
||||
Prefetch(
|
||||
"project_projectmember",
|
||||
queryset=ProjectMember.objects.filter(
|
||||
workspace__slug=slug,
|
||||
is_active=True,
|
||||
).select_related("member"),
|
||||
to_attr="members_list",
|
||||
)
|
||||
)
|
||||
.order_by("sort_order", "name")
|
||||
)
|
||||
if request.GET.get("per_page", False) and request.GET.get("cursor", False):
|
||||
@ -677,6 +677,25 @@ class ProjectMemberViewSet(BaseViewSet):
|
||||
)
|
||||
)
|
||||
|
||||
# Check if the user is already a member of the project and is inactive
|
||||
if ProjectMember.objects.filter(
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
member_id=member.get("member_id"),
|
||||
is_active=False,
|
||||
).exists():
|
||||
member_detail = ProjectMember.objects.get(
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
member_id=member.get("member_id"),
|
||||
is_active=False,
|
||||
)
|
||||
# Check if the user has not deactivated the account
|
||||
user = User.objects.filter(pk=member.get("member_id")).first()
|
||||
if user.is_active:
|
||||
member_detail.is_active = True
|
||||
member_detail.save(update_fields=["is_active"])
|
||||
|
||||
project_members = ProjectMember.objects.bulk_create(
|
||||
bulk_project_members,
|
||||
batch_size=10,
|
||||
|
@ -70,6 +70,7 @@ from plane.app.permissions import (
|
||||
WorkSpaceAdminPermission,
|
||||
WorkspaceEntityPermission,
|
||||
WorkspaceViewerPermission,
|
||||
WorkspaceUserPermission,
|
||||
)
|
||||
from plane.bgtasks.workspace_invitation_task import workspace_invitation
|
||||
from plane.utils.issue_filters import issue_filters
|
||||
@ -501,6 +502,18 @@ class WorkSpaceMemberViewSet(BaseViewSet):
|
||||
WorkspaceEntityPermission,
|
||||
]
|
||||
|
||||
def get_permissions(self):
|
||||
if self.action == "leave":
|
||||
self.permission_classes = [
|
||||
WorkspaceUserPermission,
|
||||
]
|
||||
else:
|
||||
self.permission_classes = [
|
||||
WorkspaceEntityPermission,
|
||||
]
|
||||
|
||||
return super(WorkSpaceMemberViewSet, self).get_permissions()
|
||||
|
||||
search_fields = [
|
||||
"member__display_name",
|
||||
"member__first_name",
|
||||
|
@ -65,7 +65,7 @@ def send_export_email(email, slug, csv_buffer, rows):
|
||||
port=int(EMAIL_PORT),
|
||||
username=EMAIL_HOST_USER,
|
||||
password=EMAIL_HOST_PASSWORD,
|
||||
use_tls=bool(EMAIL_USE_TLS),
|
||||
use_tls=EMAIL_USE_TLS == "1",
|
||||
)
|
||||
|
||||
msg = EmailMultiAlternatives(
|
||||
|
@ -51,7 +51,7 @@ def forgot_password(first_name, email, uidb64, token, current_site):
|
||||
port=int(EMAIL_PORT),
|
||||
username=EMAIL_HOST_USER,
|
||||
password=EMAIL_HOST_PASSWORD,
|
||||
use_tls=bool(EMAIL_USE_TLS),
|
||||
use_tls=EMAIL_USE_TLS == "1",
|
||||
)
|
||||
|
||||
msg = EmailMultiAlternatives(
|
||||
|
@ -41,7 +41,7 @@ def magic_link(email, key, token, current_site):
|
||||
port=int(EMAIL_PORT),
|
||||
username=EMAIL_HOST_USER,
|
||||
password=EMAIL_HOST_PASSWORD,
|
||||
use_tls=bool(EMAIL_USE_TLS),
|
||||
use_tls=EMAIL_USE_TLS == "1",
|
||||
)
|
||||
|
||||
msg = EmailMultiAlternatives(
|
||||
|
@ -60,7 +60,7 @@ def project_invitation(email, project_id, token, current_site, invitor):
|
||||
port=int(EMAIL_PORT),
|
||||
username=EMAIL_HOST_USER,
|
||||
password=EMAIL_HOST_PASSWORD,
|
||||
use_tls=bool(EMAIL_USE_TLS),
|
||||
use_tls=EMAIL_USE_TLS == "1",
|
||||
)
|
||||
|
||||
msg = EmailMultiAlternatives(
|
||||
|
@ -70,7 +70,7 @@ def workspace_invitation(email, workspace_id, token, current_site, invitor):
|
||||
port=int(EMAIL_PORT),
|
||||
username=EMAIL_HOST_USER,
|
||||
password=EMAIL_HOST_PASSWORD,
|
||||
use_tls=bool(EMAIL_USE_TLS),
|
||||
use_tls=EMAIL_USE_TLS == "1",
|
||||
)
|
||||
|
||||
msg = EmailMultiAlternatives(
|
||||
|
@ -12,7 +12,6 @@ volumes:
|
||||
|
||||
services:
|
||||
plane-redis:
|
||||
container_name: plane-redis
|
||||
image: redis:6.2.7-alpine
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
@ -21,7 +20,6 @@ services:
|
||||
- redisdata:/data
|
||||
|
||||
plane-minio:
|
||||
container_name: plane-minio
|
||||
image: minio/minio
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
@ -36,7 +34,6 @@ services:
|
||||
MINIO_ROOT_PASSWORD: ${AWS_SECRET_ACCESS_KEY}
|
||||
|
||||
plane-db:
|
||||
container_name: plane-db
|
||||
image: postgres:15.2-alpine
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
@ -53,7 +50,6 @@ services:
|
||||
PGDATA: /var/lib/postgresql/data
|
||||
|
||||
web:
|
||||
container_name: web
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./web/Dockerfile.dev
|
||||
@ -61,8 +57,7 @@ services:
|
||||
networks:
|
||||
- dev_env
|
||||
volumes:
|
||||
- .:/app
|
||||
command: yarn dev --filter=web
|
||||
- ./web:/app/web
|
||||
env_file:
|
||||
- ./web/.env
|
||||
depends_on:
|
||||
@ -73,22 +68,17 @@ services:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./space/Dockerfile.dev
|
||||
container_name: space
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- dev_env
|
||||
volumes:
|
||||
- .:/app
|
||||
command: yarn dev --filter=space
|
||||
env_file:
|
||||
- ./space/.env
|
||||
- ./space:/app/space
|
||||
depends_on:
|
||||
- api
|
||||
- worker
|
||||
- web
|
||||
|
||||
api:
|
||||
container_name: api
|
||||
build:
|
||||
context: ./apiserver
|
||||
dockerfile: Dockerfile.dev
|
||||
@ -99,7 +89,7 @@ services:
|
||||
- dev_env
|
||||
volumes:
|
||||
- ./apiserver:/code
|
||||
command: /bin/sh -c "python manage.py migrate && python manage.py runserver 0.0.0.0:8000 --settings=plane.settings.local"
|
||||
# command: /bin/sh -c "python manage.py migrate && python manage.py runserver 0.0.0.0:8000 --settings=plane.settings.local"
|
||||
env_file:
|
||||
- ./apiserver/.env
|
||||
depends_on:
|
||||
@ -107,7 +97,6 @@ services:
|
||||
- plane-redis
|
||||
|
||||
worker:
|
||||
container_name: bgworker
|
||||
build:
|
||||
context: ./apiserver
|
||||
dockerfile: Dockerfile.dev
|
||||
@ -127,7 +116,6 @@ services:
|
||||
- plane-redis
|
||||
|
||||
beat-worker:
|
||||
container_name: beatworker
|
||||
build:
|
||||
context: ./apiserver
|
||||
dockerfile: Dockerfile.dev
|
||||
@ -147,10 +135,9 @@ services:
|
||||
- plane-redis
|
||||
|
||||
proxy:
|
||||
container_name: proxy
|
||||
build:
|
||||
context: ./nginx
|
||||
dockerfile: Dockerfile
|
||||
dockerfile: Dockerfile.dev
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- dev_env
|
||||
|
10
nginx/Dockerfile.dev
Normal file
10
nginx/Dockerfile.dev
Normal file
@ -0,0 +1,10 @@
|
||||
FROM nginx:1.25.0-alpine
|
||||
|
||||
RUN rm /etc/nginx/conf.d/default.conf
|
||||
COPY nginx.conf.dev /etc/nginx/nginx.conf.template
|
||||
|
||||
COPY ./env.sh /docker-entrypoint.sh
|
||||
|
||||
RUN chmod +x /docker-entrypoint.sh
|
||||
# Update all environment variables
|
||||
CMD ["/docker-entrypoint.sh"]
|
@ -18,7 +18,7 @@ server {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
location /space/ {
|
||||
location /spaces/ {
|
||||
proxy_pass http://localhost:4000/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
|
36
nginx/nginx.conf.dev
Normal file
36
nginx/nginx.conf.dev
Normal file
@ -0,0 +1,36 @@
|
||||
events {
|
||||
}
|
||||
|
||||
http {
|
||||
sendfile on;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
root /www/data/;
|
||||
access_log /var/log/nginx/access.log;
|
||||
|
||||
client_max_body_size ${FILE_SIZE_LIMIT};
|
||||
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header Referrer-Policy "no-referrer-when-downgrade" always;
|
||||
add_header Permissions-Policy "interest-cohort=()" always;
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||
|
||||
location / {
|
||||
proxy_pass http://web:3000/;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://api:8000/api/;
|
||||
}
|
||||
|
||||
location /spaces/ {
|
||||
rewrite ^/spaces/?$ /spaces/login break;
|
||||
proxy_pass http://space:4000/spaces/;
|
||||
}
|
||||
|
||||
location /${BUCKET_NAME}/ {
|
||||
proxy_pass http://plane-minio:9000/uploads/;
|
||||
}
|
||||
}
|
||||
}
|
1
setup.sh
1
setup.sh
@ -6,7 +6,6 @@ export LC_ALL=C
|
||||
export LC_CTYPE=C
|
||||
|
||||
cp ./web/.env.example ./web/.env
|
||||
cp ./space/.env.example ./space/.env
|
||||
cp ./apiserver/.env.example ./apiserver/.env
|
||||
|
||||
# Generate the SECRET_KEY that will be used by django
|
||||
|
@ -7,5 +7,8 @@ WORKDIR /app
|
||||
COPY . .
|
||||
RUN yarn global add turbo
|
||||
RUN yarn install
|
||||
EXPOSE 3000
|
||||
EXPOSE 4000
|
||||
ENV NEXT_PUBLIC_DEPLOY_WITH_NGINX=1
|
||||
|
||||
VOLUME [ "/app/node_modules", "/app/space/node_modules"]
|
||||
CMD ["yarn","dev", "--filter=space"]
|
||||
|
@ -8,4 +8,5 @@ COPY . .
|
||||
RUN yarn global add turbo
|
||||
RUN yarn install
|
||||
EXPOSE 3000
|
||||
VOLUME [ "/app/node_modules", "/app/web/node_modules" ]
|
||||
CMD ["yarn", "dev", "--filter=web"]
|
||||
|
@ -4,8 +4,8 @@ import { useTheme } from "next-themes";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { Trash2 } from "lucide-react";
|
||||
import { mutate } from "swr";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useUser } from "hooks/store";
|
||||
// ui
|
||||
import { Button } from "@plane/ui";
|
||||
// hooks
|
||||
@ -22,9 +22,7 @@ export const DeactivateAccountModal: React.FC<Props> = (props) => {
|
||||
// states
|
||||
const [isDeactivating, setIsDeactivating] = useState(false);
|
||||
|
||||
const {
|
||||
user: { deactivateAccount },
|
||||
} = useMobxStore();
|
||||
const { deactivateAccount } = useUser();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// services
|
||||
import { AuthService } from "services/auth.service";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
// components
|
||||
import { GitHubSignInButton, GoogleSignInButton } from "components/account";
|
||||
@ -21,8 +20,8 @@ export const OAuthOptions: React.FC<Props> = observer((props) => {
|
||||
const { setToastAlert } = useToast();
|
||||
// mobx store
|
||||
const {
|
||||
appConfig: { envConfig },
|
||||
} = useMobxStore();
|
||||
config: { envConfig },
|
||||
} = useApplication();
|
||||
|
||||
const handleGoogleSignIn = async ({ clientId, credential }: any) => {
|
||||
try {
|
||||
|
@ -1,8 +1,7 @@
|
||||
import React, { useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
import useSignInRedirection from "hooks/use-sign-in-redirection";
|
||||
// components
|
||||
import { LatestFeatureBlock } from "components/common";
|
||||
@ -38,8 +37,8 @@ export const SignInRoot = observer(() => {
|
||||
const { handleRedirection } = useSignInRedirection();
|
||||
// mobx store
|
||||
const {
|
||||
appConfig: { envConfig },
|
||||
} = useMobxStore();
|
||||
config: { envConfig },
|
||||
} = useApplication();
|
||||
|
||||
const isOAuthEnabled = envConfig && (envConfig.google_client_id || envConfig.github_client_id);
|
||||
|
||||
|
@ -1,9 +1,7 @@
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Control, Controller, UseFormSetValue } from "react-hook-form";
|
||||
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useProject } from "hooks/store";
|
||||
// components
|
||||
import { SelectProject, SelectSegment, SelectXAxis, SelectYAxis } from "components/analytics";
|
||||
// types
|
||||
@ -20,12 +18,7 @@ type Props = {
|
||||
export const CustomAnalyticsSelectBar: React.FC<Props> = observer((props) => {
|
||||
const { control, setValue, params, fullScreen, isProjectLevel } = props;
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const { project: projectStore } = useMobxStore();
|
||||
|
||||
const projectsList = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : null;
|
||||
const { workspaceProjects } = useProject();
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -40,7 +33,11 @@ export const CustomAnalyticsSelectBar: React.FC<Props> = observer((props) => {
|
||||
name="project"
|
||||
control={control}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<SelectProject value={value ?? undefined} onChange={onChange} projects={projectsList ?? undefined} />
|
||||
<SelectProject
|
||||
value={value ?? undefined}
|
||||
onChange={onChange}
|
||||
projectIds={workspaceProjects ?? undefined}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
@ -1,25 +1,33 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useProject } from "hooks/store";
|
||||
// ui
|
||||
import { CustomSearchSelect } from "@plane/ui";
|
||||
// types
|
||||
import { IProject } from "types";
|
||||
|
||||
type Props = {
|
||||
value: string[] | undefined;
|
||||
onChange: (val: string[] | null) => void;
|
||||
projects: IProject[] | undefined;
|
||||
projectIds: string[] | undefined;
|
||||
};
|
||||
|
||||
export const SelectProject: React.FC<Props> = ({ value, onChange, projects }) => {
|
||||
const options = projects?.map((project) => ({
|
||||
value: project.id,
|
||||
query: project.name + project.identifier,
|
||||
content: (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[0.65rem] text-custom-text-200">{project.identifier}</span>
|
||||
{project.name}
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
export const SelectProject: React.FC<Props> = observer((props) => {
|
||||
const { value, onChange, projectIds } = props;
|
||||
const { getProjectById } = useProject();
|
||||
|
||||
const options = projectIds?.map((projectId) => {
|
||||
const projectDetails = getProjectById(projectId);
|
||||
|
||||
return {
|
||||
value: projectDetails?.id,
|
||||
query: `${projectDetails?.name} ${projectDetails?.identifier}`,
|
||||
content: (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[0.65rem] text-custom-text-200">{projectDetails?.identifier}</span>
|
||||
{projectDetails?.name}
|
||||
</div>
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<CustomSearchSelect
|
||||
@ -28,9 +36,9 @@ export const SelectProject: React.FC<Props> = ({ value, onChange, projects }) =>
|
||||
options={options}
|
||||
label={
|
||||
value && value.length > 0
|
||||
? projects
|
||||
?.filter((p) => value.includes(p.id))
|
||||
.map((p) => p.identifier)
|
||||
? projectIds
|
||||
?.filter((p) => value.includes(p))
|
||||
.map((p) => getProjectById(p)?.name)
|
||||
.join(", ")
|
||||
: "All projects"
|
||||
}
|
||||
@ -38,4 +46,4 @@ export const SelectProject: React.FC<Props> = ({ value, onChange, projects }) =>
|
||||
multiple
|
||||
/>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -1,65 +1,74 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useProject } from "hooks/store";
|
||||
// icons
|
||||
import { Contrast, LayoutGrid, Users } from "lucide-react";
|
||||
// helpers
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
import { truncateText } from "helpers/string.helper";
|
||||
// types
|
||||
import { IProject } from "types";
|
||||
|
||||
type Props = {
|
||||
projects: IProject[];
|
||||
projectIds: string[];
|
||||
};
|
||||
|
||||
export const CustomAnalyticsSidebarProjectsList: React.FC<Props> = (props) => {
|
||||
const { projects } = props;
|
||||
export const CustomAnalyticsSidebarProjectsList: React.FC<Props> = observer((props) => {
|
||||
const { projectIds } = props;
|
||||
|
||||
const { getProjectById } = useProject();
|
||||
|
||||
return (
|
||||
<div className="hidden h-full overflow-hidden md:flex md:flex-col">
|
||||
<h4 className="font-medium">Selected Projects</h4>
|
||||
<div className="mt-4 h-full space-y-6 overflow-y-auto">
|
||||
{projects.map((project) => (
|
||||
<div key={project.id} className="w-full">
|
||||
<div className="flex items-center gap-1 text-sm">
|
||||
{project.emoji ? (
|
||||
<span className="grid h-6 w-6 flex-shrink-0 place-items-center">{renderEmoji(project.emoji)}</span>
|
||||
) : project.icon_prop ? (
|
||||
<div className="grid h-6 w-6 flex-shrink-0 place-items-center">{renderEmoji(project.icon_prop)}</div>
|
||||
) : (
|
||||
<span className="mr-1 grid h-6 w-6 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
|
||||
{project?.name.charAt(0)}
|
||||
</span>
|
||||
)}
|
||||
<h5 className="flex items-center gap-1">
|
||||
<p className="break-words">{truncateText(project.name, 20)}</p>
|
||||
<span className="ml-1 text-xs text-custom-text-200">({project.identifier})</span>
|
||||
</h5>
|
||||
</div>
|
||||
<div className="mt-4 w-full space-y-3 pl-2">
|
||||
<div className="flex items-center justify-between gap-2 text-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
<Users className="text-custom-text-200" size={14} strokeWidth={2} />
|
||||
<h6>Total members</h6>
|
||||
</div>
|
||||
<span className="text-custom-text-200">{project.total_members}</span>
|
||||
{projectIds.map((projectId) => {
|
||||
const project = getProjectById(projectId);
|
||||
|
||||
if (!project) return;
|
||||
|
||||
return (
|
||||
<div key={projectId} className="w-full">
|
||||
<div className="flex items-center gap-1 text-sm">
|
||||
{project.emoji ? (
|
||||
<span className="grid h-6 w-6 flex-shrink-0 place-items-center">{renderEmoji(project.emoji)}</span>
|
||||
) : project.icon_prop ? (
|
||||
<div className="grid h-6 w-6 flex-shrink-0 place-items-center">{renderEmoji(project.icon_prop)}</div>
|
||||
) : (
|
||||
<span className="mr-1 grid h-6 w-6 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
|
||||
{project?.name.charAt(0)}
|
||||
</span>
|
||||
)}
|
||||
<h5 className="flex items-center gap-1">
|
||||
<p className="break-words">{truncateText(project.name, 20)}</p>
|
||||
<span className="ml-1 text-xs text-custom-text-200">({project.identifier})</span>
|
||||
</h5>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-2 text-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
<Contrast className="text-custom-text-200" size={14} strokeWidth={2} />
|
||||
<h6>Total cycles</h6>
|
||||
<div className="mt-4 w-full space-y-3 pl-2">
|
||||
<div className="flex items-center justify-between gap-2 text-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
<Users className="text-custom-text-200" size={14} strokeWidth={2} />
|
||||
<h6>Total members</h6>
|
||||
</div>
|
||||
<span className="text-custom-text-200">{project.total_members}</span>
|
||||
</div>
|
||||
<span className="text-custom-text-200">{project.total_cycles}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-2 text-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
<LayoutGrid className="text-custom-text-200" size={14} strokeWidth={2} />
|
||||
<h6>Total modules</h6>
|
||||
<div className="flex items-center justify-between gap-2 text-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
<Contrast className="text-custom-text-200" size={14} strokeWidth={2} />
|
||||
<h6>Total cycles</h6>
|
||||
</div>
|
||||
<span className="text-custom-text-200">{project.total_cycles}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-2 text-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
<LayoutGrid className="text-custom-text-200" size={14} strokeWidth={2} />
|
||||
<h6>Total modules</h6>
|
||||
</div>
|
||||
<span className="text-custom-text-200">{project.total_modules}</span>
|
||||
</div>
|
||||
<span className="text-custom-text-200">{project.total_modules}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useCycle, useModule, useProject } from "hooks/store";
|
||||
// helpers
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
import { renderShortDate } from "helpers/date-time.helper";
|
||||
@ -11,16 +10,15 @@ import { NETWORK_CHOICES } from "constants/project";
|
||||
|
||||
export const CustomAnalyticsSidebarHeader = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
|
||||
const { projectId, cycleId, moduleId } = router.query;
|
||||
|
||||
const { cycle: cycleStore, module: moduleStore, project: projectStore } = useMobxStore();
|
||||
const { getProjectById } = useProject();
|
||||
const { getCycleById } = useCycle();
|
||||
const { getModuleById } = useModule();
|
||||
|
||||
const cycleDetails = cycleId ? cycleStore.getCycleById(cycleId.toString()) : undefined;
|
||||
const moduleDetails = moduleId ? moduleStore.getModuleById(moduleId.toString()) : undefined;
|
||||
const projectDetails =
|
||||
workspaceSlug && projectId
|
||||
? projectStore.getProjectById(workspaceSlug.toString(), projectId.toString())
|
||||
: undefined;
|
||||
const cycleDetails = cycleId ? getCycleById(cycleId.toString()) : undefined;
|
||||
const moduleDetails = moduleId ? getModuleById(moduleId.toString()) : undefined;
|
||||
const projectDetails = projectId ? getProjectById(projectId.toString()) : undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -5,8 +5,8 @@ import { mutate } from "swr";
|
||||
// services
|
||||
import { AnalyticsService } from "services/analytics.service";
|
||||
// hooks
|
||||
import { useCycle, useModule, useProject, useUser } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// components
|
||||
import { CustomAnalyticsSidebarHeader, CustomAnalyticsSidebarProjectsList } from "components/analytics";
|
||||
// ui
|
||||
@ -29,172 +29,167 @@ type Props = {
|
||||
|
||||
const analyticsService = new AnalyticsService();
|
||||
|
||||
export const CustomAnalyticsSidebar: React.FC<Props> = observer(
|
||||
({ analytics, params, fullScreen, isProjectLevel = false }) => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
|
||||
export const CustomAnalyticsSidebar: React.FC<Props> = observer((props) => {
|
||||
const { analytics, params, fullScreen, isProjectLevel = false } = props;
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
|
||||
// toast alert
|
||||
const { setToastAlert } = useToast();
|
||||
// store hooks
|
||||
const { currentUser } = useUser();
|
||||
const { workspaceProjects, getProjectById } = useProject();
|
||||
const { fetchCycleDetails, getCycleById } = useCycle();
|
||||
const { fetchModuleDetails, getModuleById } = useModule();
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
const projectDetails = projectId ? getProjectById(projectId.toString()) ?? undefined : undefined;
|
||||
|
||||
const { user: userStore, project: projectStore, cycle: cycleStore, module: moduleStore } = useMobxStore();
|
||||
const trackExportAnalytics = () => {
|
||||
if (!currentUser) return;
|
||||
|
||||
const user = userStore.currentUser;
|
||||
|
||||
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined;
|
||||
const projectDetails =
|
||||
workspaceSlug && projectId
|
||||
? projectStore.getProjectById(workspaceSlug.toString(), projectId.toString()) ?? undefined
|
||||
: undefined;
|
||||
|
||||
const trackExportAnalytics = () => {
|
||||
if (!user) return;
|
||||
|
||||
const eventPayload: any = {
|
||||
workspaceSlug: workspaceSlug?.toString(),
|
||||
params: {
|
||||
x_axis: params.x_axis,
|
||||
y_axis: params.y_axis,
|
||||
group: params.segment,
|
||||
project: params.project,
|
||||
},
|
||||
};
|
||||
|
||||
if (projectDetails) {
|
||||
const workspaceDetails = projectDetails.workspace as IWorkspace;
|
||||
|
||||
eventPayload.workspaceId = workspaceDetails.id;
|
||||
eventPayload.workspaceName = workspaceDetails.name;
|
||||
eventPayload.projectId = projectDetails.id;
|
||||
eventPayload.projectIdentifier = projectDetails.identifier;
|
||||
eventPayload.projectName = projectDetails.name;
|
||||
}
|
||||
|
||||
if (cycleDetails || moduleDetails) {
|
||||
const details = cycleDetails || moduleDetails;
|
||||
|
||||
eventPayload.workspaceId = details?.workspace_detail?.id;
|
||||
eventPayload.workspaceName = details?.workspace_detail?.name;
|
||||
eventPayload.projectId = details?.project_detail.id;
|
||||
eventPayload.projectIdentifier = details?.project_detail.identifier;
|
||||
eventPayload.projectName = details?.project_detail.name;
|
||||
}
|
||||
|
||||
if (cycleDetails) {
|
||||
eventPayload.cycleId = cycleDetails.id;
|
||||
eventPayload.cycleName = cycleDetails.name;
|
||||
}
|
||||
|
||||
if (moduleDetails) {
|
||||
eventPayload.moduleId = moduleDetails.id;
|
||||
eventPayload.moduleName = moduleDetails.name;
|
||||
}
|
||||
};
|
||||
|
||||
const exportAnalytics = () => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
const data: IExportAnalyticsFormData = {
|
||||
const eventPayload: any = {
|
||||
workspaceSlug: workspaceSlug?.toString(),
|
||||
params: {
|
||||
x_axis: params.x_axis,
|
||||
y_axis: params.y_axis,
|
||||
};
|
||||
|
||||
if (params.segment) data.segment = params.segment;
|
||||
if (params.project) data.project = params.project;
|
||||
|
||||
analyticsService
|
||||
.exportAnalytics(workspaceSlug.toString(), data)
|
||||
.then((res) => {
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
message: res.message,
|
||||
});
|
||||
|
||||
trackExportAnalytics();
|
||||
})
|
||||
.catch(() =>
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: "There was some error in exporting the analytics. Please try again.",
|
||||
})
|
||||
);
|
||||
group: params.segment,
|
||||
project: params.project,
|
||||
},
|
||||
};
|
||||
|
||||
const cycleDetails = cycleId ? cycleStore.getCycleById(cycleId.toString()) : undefined;
|
||||
const moduleDetails = moduleId ? moduleStore.getModuleById(moduleId.toString()) : undefined;
|
||||
if (projectDetails) {
|
||||
const workspaceDetails = projectDetails.workspace as IWorkspace;
|
||||
|
||||
// fetch cycle details
|
||||
useEffect(() => {
|
||||
if (!workspaceSlug || !projectId || !cycleId || cycleDetails) return;
|
||||
eventPayload.workspaceId = workspaceDetails.id;
|
||||
eventPayload.workspaceName = workspaceDetails.name;
|
||||
eventPayload.projectId = projectDetails.id;
|
||||
eventPayload.projectIdentifier = projectDetails.identifier;
|
||||
eventPayload.projectName = projectDetails.name;
|
||||
}
|
||||
|
||||
cycleStore.fetchCycleWithId(workspaceSlug.toString(), projectId.toString(), cycleId.toString());
|
||||
}, [cycleId, cycleDetails, cycleStore, projectId, workspaceSlug]);
|
||||
if (cycleDetails || moduleDetails) {
|
||||
const details = cycleDetails || moduleDetails;
|
||||
|
||||
// fetch module details
|
||||
useEffect(() => {
|
||||
if (!workspaceSlug || !projectId || !moduleId || moduleDetails) return;
|
||||
eventPayload.workspaceId = details?.workspace_detail?.id;
|
||||
eventPayload.workspaceName = details?.workspace_detail?.name;
|
||||
eventPayload.projectId = details?.project_detail.id;
|
||||
eventPayload.projectIdentifier = details?.project_detail.identifier;
|
||||
eventPayload.projectName = details?.project_detail.name;
|
||||
}
|
||||
|
||||
moduleStore.fetchModuleDetails(workspaceSlug.toString(), projectId.toString(), moduleId.toString());
|
||||
}, [moduleId, moduleDetails, moduleStore, projectId, workspaceSlug]);
|
||||
if (cycleDetails) {
|
||||
eventPayload.cycleId = cycleDetails.id;
|
||||
eventPayload.cycleName = cycleDetails.name;
|
||||
}
|
||||
|
||||
const selectedProjects = params.project && params.project.length > 0 ? params.project : projects?.map((p) => p.id);
|
||||
if (moduleDetails) {
|
||||
eventPayload.moduleId = moduleDetails.id;
|
||||
eventPayload.moduleName = moduleDetails.name;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex items-center justify-between space-y-2 px-5 py-2.5 ${
|
||||
fullScreen
|
||||
? "overflow-hidden border-l border-custom-border-200 md:h-full md:flex-col md:items-start md:space-y-4 md:border-l md:border-custom-border-200 md:py-5"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
const exportAnalytics = () => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
const data: IExportAnalyticsFormData = {
|
||||
x_axis: params.x_axis,
|
||||
y_axis: params.y_axis,
|
||||
};
|
||||
|
||||
if (params.segment) data.segment = params.segment;
|
||||
if (params.project) data.project = params.project;
|
||||
|
||||
analyticsService
|
||||
.exportAnalytics(workspaceSlug.toString(), data)
|
||||
.then((res) => {
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
message: res.message,
|
||||
});
|
||||
|
||||
trackExportAnalytics();
|
||||
})
|
||||
.catch(() =>
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: "There was some error in exporting the analytics. Please try again.",
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const cycleDetails = cycleId ? getCycleById(cycleId.toString()) : undefined;
|
||||
const moduleDetails = moduleId ? getModuleById(moduleId.toString()) : undefined;
|
||||
|
||||
// fetch cycle details
|
||||
useEffect(() => {
|
||||
if (!workspaceSlug || !projectId || !cycleId || cycleDetails) return;
|
||||
|
||||
fetchCycleDetails(workspaceSlug.toString(), projectId.toString(), cycleId.toString());
|
||||
}, [cycleId, cycleDetails, fetchCycleDetails, projectId, workspaceSlug]);
|
||||
|
||||
// fetch module details
|
||||
useEffect(() => {
|
||||
if (!workspaceSlug || !projectId || !moduleId || moduleDetails) return;
|
||||
|
||||
fetchModuleDetails(workspaceSlug.toString(), projectId.toString(), moduleId.toString());
|
||||
}, [moduleId, moduleDetails, fetchModuleDetails, projectId, workspaceSlug]);
|
||||
|
||||
const selectedProjects = params.project && params.project.length > 0 ? params.project : workspaceProjects;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex items-center justify-between space-y-2 px-5 py-2.5 ${
|
||||
fullScreen
|
||||
? "overflow-hidden border-l border-custom-border-200 md:h-full md:flex-col md:items-start md:space-y-4 md:border-l md:border-custom-border-200 md:py-5"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<div className="flex items-center gap-1 rounded-md bg-custom-background-80 px-3 py-1 text-xs text-custom-text-200">
|
||||
<LayersIcon height={14} width={14} />
|
||||
{analytics ? analytics.total : "..."} Issues
|
||||
</div>
|
||||
{isProjectLevel && (
|
||||
<div className="flex items-center gap-1 rounded-md bg-custom-background-80 px-3 py-1 text-xs text-custom-text-200">
|
||||
<LayersIcon height={14} width={14} />
|
||||
{analytics ? analytics.total : "..."} Issues
|
||||
<CalendarDays className="h-3.5 w-3.5" />
|
||||
{renderShortDate(
|
||||
(cycleId
|
||||
? cycleDetails?.created_at
|
||||
: moduleId
|
||||
? moduleDetails?.created_at
|
||||
: projectDetails?.created_at) ?? ""
|
||||
)}
|
||||
</div>
|
||||
{isProjectLevel && (
|
||||
<div className="flex items-center gap-1 rounded-md bg-custom-background-80 px-3 py-1 text-xs text-custom-text-200">
|
||||
<CalendarDays className="h-3.5 w-3.5" />
|
||||
{renderShortDate(
|
||||
(cycleId
|
||||
? cycleDetails?.created_at
|
||||
: moduleId
|
||||
? moduleDetails?.created_at
|
||||
: projectDetails?.created_at) ?? ""
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="h-full w-full overflow-hidden">
|
||||
{fullScreen ? (
|
||||
<>
|
||||
{!isProjectLevel && selectedProjects && selectedProjects.length > 0 && (
|
||||
<CustomAnalyticsSidebarProjectsList
|
||||
projects={projects?.filter((p) => selectedProjects.includes(p.id)) ?? []}
|
||||
/>
|
||||
)}
|
||||
<CustomAnalyticsSidebarHeader />
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2 justify-self-end">
|
||||
<Button
|
||||
variant="neutral-primary"
|
||||
prependIcon={<RefreshCw className="h-3.5 w-3.5" />}
|
||||
onClick={() => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
mutate(ANALYTICS(workspaceSlug.toString(), params));
|
||||
}}
|
||||
>
|
||||
Refresh
|
||||
</Button>
|
||||
<Button variant="primary" prependIcon={<Download className="h-3.5 w-3.5" />} onClick={exportAnalytics}>
|
||||
Export as CSV
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
<div className="h-full w-full overflow-hidden">
|
||||
{fullScreen ? (
|
||||
<>
|
||||
{!isProjectLevel && selectedProjects && selectedProjects.length > 0 && (
|
||||
<CustomAnalyticsSidebarProjectsList projectIds={selectedProjects} />
|
||||
)}
|
||||
<CustomAnalyticsSidebarHeader />
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2 justify-self-end">
|
||||
<Button
|
||||
variant="neutral-primary"
|
||||
prependIcon={<RefreshCw className="h-3.5 w-3.5" />}
|
||||
onClick={() => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
mutate(ANALYTICS(workspaceSlug.toString(), params));
|
||||
}}
|
||||
>
|
||||
Refresh
|
||||
</Button>
|
||||
<Button variant="primary" prependIcon={<Download className="h-3.5 w-3.5" />} onClick={exportAnalytics}>
|
||||
Export as CSV
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -1,12 +1,12 @@
|
||||
import React from "react";
|
||||
// next
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useUser } from "hooks/store";
|
||||
// layouts
|
||||
import DefaultLayout from "layouts/default-layout";
|
||||
// hooks
|
||||
import useUser from "hooks/use-user";
|
||||
// images
|
||||
import ProjectNotAuthorizedImg from "public/auth/project-not-authorized.svg";
|
||||
import WorkspaceNotAuthorizedImg from "public/auth/workspace-not-authorized.svg";
|
||||
@ -16,9 +16,12 @@ type Props = {
|
||||
type: "project" | "workspace";
|
||||
};
|
||||
|
||||
export const NotAuthorizedView: React.FC<Props> = ({ actionButton, type }) => {
|
||||
const { user } = useUser();
|
||||
export const NotAuthorizedView: React.FC<Props> = observer((props) => {
|
||||
const { actionButton, type } = props;
|
||||
// router
|
||||
const { asPath: currentPath } = useRouter();
|
||||
// store hooks
|
||||
const { currentUser } = useUser();
|
||||
|
||||
return (
|
||||
<DefaultLayout>
|
||||
@ -34,9 +37,9 @@ export const NotAuthorizedView: React.FC<Props> = ({ actionButton, type }) => {
|
||||
<h1 className="text-xl font-medium text-custom-text-100">Oops! You are not authorized to view this page</h1>
|
||||
|
||||
<div className="w-full max-w-md text-base text-custom-text-200">
|
||||
{user ? (
|
||||
{currentUser ? (
|
||||
<p>
|
||||
You have signed in as {user.email}. <br />
|
||||
You have signed in as {currentUser.email}. <br />
|
||||
<Link href={`/?next=${currentPath}`}>
|
||||
<span className="font-medium text-custom-text-100">Sign in</span>
|
||||
</Link>{" "}
|
||||
@ -57,4 +60,4 @@ export const NotAuthorizedView: React.FC<Props> = ({ actionButton, type }) => {
|
||||
</div>
|
||||
</DefaultLayout>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store_legacy/root";
|
||||
// hooks
|
||||
import { useProject, useUser } from "hooks/store";
|
||||
// ui
|
||||
import { Button } from "@plane/ui";
|
||||
// icons
|
||||
@ -12,12 +11,13 @@ import { ClipboardList } from "lucide-react";
|
||||
import JoinProjectImg from "public/auth/project-not-authorized.svg";
|
||||
|
||||
export const JoinProject: React.FC = () => {
|
||||
// states
|
||||
const [isJoiningProject, setIsJoiningProject] = useState(false);
|
||||
|
||||
// store hooks
|
||||
const {
|
||||
project: projectStore,
|
||||
user: { joinProject },
|
||||
}: RootStore = useMobxStore();
|
||||
membership: { joinProject },
|
||||
} = useUser();
|
||||
const { fetchProjects } = useProject();
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
@ -28,12 +28,8 @@ export const JoinProject: React.FC = () => {
|
||||
setIsJoiningProject(true);
|
||||
|
||||
joinProject(workspaceSlug.toString(), [projectId.toString()])
|
||||
.then(() => {
|
||||
projectStore.fetchProjects(workspaceSlug.toString());
|
||||
})
|
||||
.finally(() => {
|
||||
setIsJoiningProject(false);
|
||||
});
|
||||
.then(() => fetchProjects(workspaceSlug.toString()))
|
||||
.finally(() => setIsJoiningProject(false));
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useProject, useUser } from "hooks/store";
|
||||
// component
|
||||
import { CustomSelect, Loader, ToggleSwitch } from "@plane/ui";
|
||||
import { SelectMonthModal } from "components/automation";
|
||||
@ -23,13 +23,13 @@ export const AutoArchiveAutomation: React.FC<Props> = observer((props) => {
|
||||
const { handleChange } = props;
|
||||
// states
|
||||
const [monthModal, setmonthModal] = useState(false);
|
||||
// store hooks
|
||||
const {
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
const { currentProjectDetails } = useProject();
|
||||
|
||||
const { user: userStore, project: projectStore } = useMobxStore();
|
||||
|
||||
const projectDetails = projectStore.currentProjectDetails;
|
||||
const userRole = userStore.currentProjectRole;
|
||||
|
||||
const isAdmin = userRole === EUserWorkspaceRoles.ADMIN;
|
||||
const isAdmin = currentProjectRole === EUserWorkspaceRoles.ADMIN;
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -54,24 +54,28 @@ export const AutoArchiveAutomation: React.FC<Props> = observer((props) => {
|
||||
</div>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
value={projectDetails?.archive_in !== 0}
|
||||
value={currentProjectDetails?.archive_in !== 0}
|
||||
onChange={() =>
|
||||
projectDetails?.archive_in === 0 ? handleChange({ archive_in: 1 }) : handleChange({ archive_in: 0 })
|
||||
currentProjectDetails?.archive_in === 0
|
||||
? handleChange({ archive_in: 1 })
|
||||
: handleChange({ archive_in: 0 })
|
||||
}
|
||||
size="sm"
|
||||
disabled={!isAdmin}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{projectDetails ? (
|
||||
projectDetails.archive_in !== 0 && (
|
||||
{currentProjectDetails ? (
|
||||
currentProjectDetails.archive_in !== 0 && (
|
||||
<div className="ml-12">
|
||||
<div className="flex w-full items-center justify-between gap-2 rounded border border-custom-border-200 bg-custom-background-90 px-5 py-4">
|
||||
<div className="w-1/2 text-sm font-medium">Auto-archive issues that are closed for</div>
|
||||
<div className="w-1/2">
|
||||
<CustomSelect
|
||||
value={projectDetails?.archive_in}
|
||||
label={`${projectDetails?.archive_in} ${projectDetails?.archive_in === 1 ? "Month" : "Months"}`}
|
||||
value={currentProjectDetails?.archive_in}
|
||||
label={`${currentProjectDetails?.archive_in} ${
|
||||
currentProjectDetails?.archive_in === 1 ? "Month" : "Months"
|
||||
}`}
|
||||
onChange={(val: number) => {
|
||||
handleChange({ archive_in: val });
|
||||
}}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useProject, useProjectState, useUser } from "hooks/store";
|
||||
// component
|
||||
import { SelectMonthModal } from "components/automation";
|
||||
import { CustomSelect, CustomSearchSelect, ToggleSwitch, StateGroupIcon, DoubleCircleIcon, Loader } from "@plane/ui";
|
||||
@ -21,15 +21,16 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
|
||||
const { handleChange } = props;
|
||||
// states
|
||||
const [monthModal, setmonthModal] = useState(false);
|
||||
// store hooks
|
||||
const {
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
const { currentProjectDetails } = useProject();
|
||||
const { projectStates } = useProjectState();
|
||||
|
||||
const { user: userStore, project: projectStore, projectState: projectStateStore } = useMobxStore();
|
||||
|
||||
const userRole = userStore.currentProjectRole;
|
||||
const projectDetails = projectStore.currentProjectDetails;
|
||||
// const stateGroups = projectStateStore.groupedProjectStates ?? undefined;
|
||||
const states = projectStateStore.projectStates;
|
||||
|
||||
const options = states
|
||||
const options = projectStates
|
||||
?.filter((state) => state.group === "cancelled")
|
||||
.map((state) => ({
|
||||
value: state.id,
|
||||
@ -44,17 +45,17 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
|
||||
|
||||
const multipleOptions = (options ?? []).length > 1;
|
||||
|
||||
const defaultState = states?.find((s) => s.group === "cancelled")?.id || null;
|
||||
const defaultState = projectStates?.find((s) => s.group === "cancelled")?.id || null;
|
||||
|
||||
const selectedOption = states?.find((s) => s.id === projectDetails?.default_state ?? defaultState);
|
||||
const currentDefaultState = states?.find((s) => s.id === defaultState);
|
||||
const selectedOption = projectStates?.find((s) => s.id === currentProjectDetails?.default_state ?? defaultState);
|
||||
const currentDefaultState = projectStates?.find((s) => s.id === defaultState);
|
||||
|
||||
const initialValues: Partial<IProject> = {
|
||||
close_in: 1,
|
||||
default_state: defaultState,
|
||||
};
|
||||
|
||||
const isAdmin = userRole === EUserWorkspaceRoles.ADMIN;
|
||||
const isAdmin = currentProjectRole === EUserWorkspaceRoles.ADMIN;
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -79,9 +80,9 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
|
||||
</div>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
value={projectDetails?.close_in !== 0}
|
||||
value={currentProjectDetails?.close_in !== 0}
|
||||
onChange={() =>
|
||||
projectDetails?.close_in === 0
|
||||
currentProjectDetails?.close_in === 0
|
||||
? handleChange({ close_in: 1, default_state: defaultState })
|
||||
: handleChange({ close_in: 0, default_state: null })
|
||||
}
|
||||
@ -90,16 +91,18 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{projectDetails ? (
|
||||
projectDetails.close_in !== 0 && (
|
||||
{currentProjectDetails ? (
|
||||
currentProjectDetails.close_in !== 0 && (
|
||||
<div className="ml-12">
|
||||
<div className="flex flex-col rounded border border-custom-border-200 bg-custom-background-90">
|
||||
<div className="flex w-full items-center justify-between gap-2 px-5 py-4">
|
||||
<div className="w-1/2 text-sm font-medium">Auto-close issues that are inactive for</div>
|
||||
<div className="w-1/2">
|
||||
<CustomSelect
|
||||
value={projectDetails?.close_in}
|
||||
label={`${projectDetails?.close_in} ${projectDetails?.close_in === 1 ? "Month" : "Months"}`}
|
||||
value={currentProjectDetails?.close_in}
|
||||
label={`${currentProjectDetails?.close_in} ${
|
||||
currentProjectDetails?.close_in === 1 ? "Month" : "Months"
|
||||
}`}
|
||||
onChange={(val: number) => {
|
||||
handleChange({ close_in: val });
|
||||
}}
|
||||
@ -118,7 +121,7 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
|
||||
className="flex w-full select-none items-center rounded px-1 py-1.5 text-custom-text-200 hover:bg-custom-background-80"
|
||||
onClick={() => setmonthModal(true)}
|
||||
>
|
||||
Customise Time Range
|
||||
Customize Time Range
|
||||
</button>
|
||||
</>
|
||||
</CustomSelect>
|
||||
@ -129,7 +132,7 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
|
||||
<div className="w-1/2 text-sm font-medium">Auto-close Status</div>
|
||||
<div className="w-1/2 ">
|
||||
<CustomSearchSelect
|
||||
value={projectDetails?.default_state ?? defaultState}
|
||||
value={currentProjectDetails?.default_state ?? defaultState}
|
||||
label={
|
||||
<div className="flex items-center gap-2">
|
||||
{selectedOption ? (
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Command } from "cmdk";
|
||||
import { FileText, GithubIcon, MessageSquare, Rocket } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
// ui
|
||||
import { DiscordIcon } from "@plane/ui";
|
||||
|
||||
@ -14,7 +14,7 @@ export const CommandPaletteHelpActions: React.FC<Props> = (props) => {
|
||||
|
||||
const {
|
||||
commandPalette: { toggleShortcutModal },
|
||||
} = useMobxStore();
|
||||
} = useApplication();
|
||||
|
||||
return (
|
||||
<Command.Group heading="Help">
|
||||
|
@ -5,6 +5,8 @@ import { LinkIcon, Signal, Trash2, UserMinus2, UserPlus2 } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication, useUser } from "hooks/store";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// ui
|
||||
import { DoubleCircleIcon, UserGroupIcon } from "@plane/ui";
|
||||
@ -29,10 +31,12 @@ export const CommandPaletteIssueActions: React.FC<Props> = observer((props) => {
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
|
||||
const {
|
||||
commandPalette: { toggleCommandPaletteModal, toggleDeleteIssueModal },
|
||||
projectIssues: { updateIssue },
|
||||
user: { currentUser },
|
||||
} = useMobxStore();
|
||||
const {
|
||||
commandPalette: { toggleCommandPaletteModal, toggleDeleteIssueModal },
|
||||
} = useApplication();
|
||||
const { currentUser } = useUser();
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Command } from "cmdk";
|
||||
import { ContrastIcon, FileText } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
// ui
|
||||
import { DiceIcon, PhotoFilterIcon } from "@plane/ui";
|
||||
|
||||
@ -14,8 +14,8 @@ export const CommandPaletteProjectActions: React.FC<Props> = (props) => {
|
||||
|
||||
const {
|
||||
commandPalette: { toggleCreateCycleModal, toggleCreateModuleModal, toggleCreatePageModal, toggleCreateViewModal },
|
||||
trackEvent: { setTrackElement },
|
||||
} = useMobxStore();
|
||||
eventTracker: { setTrackElement },
|
||||
} = useApplication();
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -4,8 +4,8 @@ import { useTheme } from "next-themes";
|
||||
import { Settings } from "lucide-react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useUser } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// constants
|
||||
import { THEME_OPTIONS } from "constants/themes";
|
||||
|
||||
@ -18,9 +18,7 @@ export const CommandPaletteThemeActions: FC<Props> = observer((props) => {
|
||||
// states
|
||||
const [mounted, setMounted] = useState(false);
|
||||
// store
|
||||
const {
|
||||
user: { updateCurrentUserTheme },
|
||||
} = useMobxStore();
|
||||
const { updateCurrentUserTheme } = useUser();
|
||||
// hooks
|
||||
const { setTheme } = useTheme();
|
||||
const { setToastAlert } = useToast();
|
||||
|
@ -5,8 +5,8 @@ import { Command } from "cmdk";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { FolderPlus, Search, Settings } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
// services
|
||||
import { WorkspaceService } from "services/workspace.service";
|
||||
import { IssueService } from "services/issue";
|
||||
@ -62,8 +62,8 @@ export const CommandModal: React.FC = observer(() => {
|
||||
toggleCreateIssueModal,
|
||||
toggleCreateProjectModal,
|
||||
},
|
||||
trackEvent: { setTrackElement },
|
||||
} = useMobxStore();
|
||||
eventTracker: { setTrackElement },
|
||||
} = useApplication();
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
|
@ -3,6 +3,7 @@ import { useRouter } from "next/router";
|
||||
import useSWR from "swr";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useApplication, useUser } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
// components
|
||||
import { CommandModal, ShortcutsModal } from "components/command-palette";
|
||||
@ -19,8 +20,6 @@ import { copyTextToClipboard } from "helpers/string.helper";
|
||||
import { IssueService } from "services/issue";
|
||||
// fetch keys
|
||||
import { ISSUE_DETAILS } from "constants/fetch-keys";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
// services
|
||||
const issueService = new IssueService();
|
||||
@ -28,14 +27,14 @@ const issueService = new IssueService();
|
||||
export const CommandPalette: FC = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, issueId, cycleId, moduleId } = router.query;
|
||||
// store
|
||||
|
||||
const {
|
||||
commandPalette,
|
||||
theme: { toggleSidebar },
|
||||
user: { currentUser },
|
||||
trackEvent: { setTrackElement },
|
||||
projectIssues: { removeIssue },
|
||||
} = useMobxStore();
|
||||
eventTracker: { setTrackElement },
|
||||
} = useApplication();
|
||||
const { currentUser } = useUser();
|
||||
|
||||
const {
|
||||
toggleCommandPaletteModal,
|
||||
isCreateIssueModalOpen,
|
@ -1,5 +1,5 @@
|
||||
export * from "./actions";
|
||||
export * from "./shortcuts-modal";
|
||||
export * from "./command-modal";
|
||||
export * from "./command-pallette";
|
||||
export * from "./command-palette";
|
||||
export * from "./helpers";
|
||||
|
@ -6,8 +6,8 @@ import useSWR from "swr";
|
||||
import { useDropzone } from "react-dropzone";
|
||||
import { Tab, Transition, Popover } from "@headlessui/react";
|
||||
import { Control, Controller } from "react-hook-form";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication, useWorkspace } from "hooks/store";
|
||||
// services
|
||||
import { FileService } from "services/file.service";
|
||||
// hooks
|
||||
@ -45,25 +45,24 @@ const fileService = new FileService();
|
||||
|
||||
export const ImagePickerPopover: React.FC<Props> = observer((props) => {
|
||||
const { label, value, control, onChange, disabled = false } = props;
|
||||
|
||||
// states
|
||||
const [image, setImage] = useState<File | null>(null);
|
||||
const [isImageUploading, setIsImageUploading] = useState(false);
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [searchParams, setSearchParams] = useState("");
|
||||
const [formData, setFormData] = useState({
|
||||
search: "",
|
||||
});
|
||||
|
||||
// refs
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
// store hooks
|
||||
const {
|
||||
workspace: { currentWorkspace },
|
||||
appConfig: { envConfig },
|
||||
} = useMobxStore();
|
||||
config: { envConfig },
|
||||
} = useApplication();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
const { data: unsplashImages, error: unsplashError } = useSWR(
|
||||
`UNSPLASH_IMAGES_${searchParams}`,
|
||||
|
@ -2,8 +2,8 @@ import React, { useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useDropzone } from "react-dropzone";
|
||||
import { Transition, Dialog } from "@headlessui/react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
// services
|
||||
import { FileService } from "services/file.service";
|
||||
// hooks
|
||||
@ -32,12 +32,12 @@ export const UserImageUploadModal: React.FC<Props> = observer((props) => {
|
||||
// states
|
||||
const [image, setImage] = useState<File | null>(null);
|
||||
const [isImageUploading, setIsImageUploading] = useState(false);
|
||||
|
||||
// toast alert
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
// store hooks
|
||||
const {
|
||||
appConfig: { envConfig },
|
||||
} = useMobxStore();
|
||||
config: { envConfig },
|
||||
} = useApplication();
|
||||
|
||||
const onDrop = (acceptedFiles: File[]) => setImage(acceptedFiles[0]);
|
||||
|
||||
|
@ -3,8 +3,8 @@ import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useDropzone } from "react-dropzone";
|
||||
import { Transition, Dialog } from "@headlessui/react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication, useWorkspace } from "hooks/store";
|
||||
// services
|
||||
import { FileService } from "services/file.service";
|
||||
// hooks
|
||||
@ -40,9 +40,9 @@ export const WorkspaceImageUploadModal: React.FC<Props> = observer((props) => {
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const {
|
||||
workspace: { currentWorkspace },
|
||||
appConfig: { envConfig },
|
||||
} = useMobxStore();
|
||||
config: { envConfig },
|
||||
} = useApplication();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
const onDrop = (acceptedFiles: File[]) => setImage(acceptedFiles[0]);
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { useTheme } from "next-themes";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useUser } from "hooks/store";
|
||||
// ui
|
||||
import { Button, InputColorPicker } from "@plane/ui";
|
||||
// types
|
||||
@ -25,8 +25,8 @@ const inputRules = {
|
||||
};
|
||||
|
||||
export const CustomThemeSelector: React.FC = observer(() => {
|
||||
const { user: userStore } = useMobxStore();
|
||||
const userTheme = userStore?.currentUser?.theme;
|
||||
const { currentUser, updateCurrentUser } = useUser();
|
||||
const userTheme = currentUser?.theme;
|
||||
// hooks
|
||||
const { setTheme } = useTheme();
|
||||
|
||||
@ -61,7 +61,7 @@ export const CustomThemeSelector: React.FC = observer(() => {
|
||||
|
||||
setTheme("custom");
|
||||
|
||||
return userStore.updateCurrentUser({ theme: payload });
|
||||
return updateCurrentUser({ theme: payload });
|
||||
};
|
||||
|
||||
const handleValueChange = (val: string | undefined, onChange: any) => {
|
||||
|
@ -3,9 +3,9 @@ import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import useSWR from "swr";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { useApplication } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
// ui
|
||||
import { SingleProgressStats } from "components/core";
|
||||
@ -67,15 +67,18 @@ interface IActiveCycleDetails {
|
||||
}
|
||||
|
||||
export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props) => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
|
||||
const { workspaceSlug, projectId } = props;
|
||||
|
||||
const { cycle: cycleStore, commandPalette: commandPaletteStore } = useMobxStore();
|
||||
|
||||
// store hooks
|
||||
const { cycle: cycleStore } = useMobxStore();
|
||||
const {
|
||||
commandPalette: { toggleCreateCycleModal },
|
||||
} = useApplication();
|
||||
// toast alert
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
useSWR(
|
||||
const { isLoading } = useSWR(
|
||||
workspaceSlug && projectId ? `ACTIVE_CYCLE_ISSUE_${projectId}_CURRENT` : null,
|
||||
workspaceSlug && projectId ? () => cycleStore.fetchCycles(workspaceSlug, projectId, "current") : null
|
||||
);
|
||||
@ -94,7 +97,7 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
|
||||
// : null
|
||||
// ) as { data: IIssue[] | undefined };
|
||||
|
||||
if (!cycle)
|
||||
if (!cycle && isLoading)
|
||||
return (
|
||||
<Loader>
|
||||
<Loader.Item height="250px" />
|
||||
@ -118,7 +121,7 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
|
||||
<button
|
||||
type="button"
|
||||
className="text-sm text-custom-primary-100 outline-none"
|
||||
onClick={() => commandPaletteStore.toggleCreateCycleModal(true)}
|
||||
onClick={() => toggleCreateCycleModal(true)}
|
||||
>
|
||||
Create a new cycle
|
||||
</button>
|
||||
@ -187,12 +190,12 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
|
||||
cycleStatus === "current"
|
||||
? "#09A953"
|
||||
: cycleStatus === "upcoming"
|
||||
? "#F7AE59"
|
||||
: cycleStatus === "completed"
|
||||
? "#3F76FF"
|
||||
: cycleStatus === "draft"
|
||||
? "rgb(var(--color-text-200))"
|
||||
: ""
|
||||
? "#F7AE59"
|
||||
: cycleStatus === "completed"
|
||||
? "#3F76FF"
|
||||
: cycleStatus === "draft"
|
||||
? "rgb(var(--color-text-200))"
|
||||
: ""
|
||||
}`}
|
||||
/>
|
||||
</span>
|
||||
@ -207,12 +210,12 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
|
||||
cycleStatus === "current"
|
||||
? "bg-green-600/5 text-green-600"
|
||||
: cycleStatus === "upcoming"
|
||||
? "bg-orange-300/5 text-orange-300"
|
||||
: cycleStatus === "completed"
|
||||
? "bg-blue-500/5 text-blue-500"
|
||||
: cycleStatus === "draft"
|
||||
? "bg-neutral-400/5 text-neutral-400"
|
||||
: ""
|
||||
? "bg-orange-300/5 text-orange-300"
|
||||
: cycleStatus === "completed"
|
||||
? "bg-blue-500/5 text-blue-500"
|
||||
: cycleStatus === "draft"
|
||||
? "bg-neutral-400/5 text-neutral-400"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
{cycleStatus === "current" ? (
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
// components
|
||||
import { CyclePeekOverview, CyclesBoardCard } from "components/cycles";
|
||||
// types
|
||||
@ -17,8 +17,8 @@ export interface ICyclesBoard {
|
||||
|
||||
export const CyclesBoard: FC<ICyclesBoard> = observer((props) => {
|
||||
const { cycles, filter, workspaceSlug, projectId, peekCycle } = props;
|
||||
|
||||
const { commandPalette: commandPaletteStore } = useMobxStore();
|
||||
// store hooks
|
||||
const { commandPalette: commandPaletteStore } = useApplication();
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
// components
|
||||
import { CyclePeekOverview, CyclesListItem } from "components/cycles";
|
||||
// ui
|
||||
@ -18,11 +18,11 @@ export interface ICyclesList {
|
||||
|
||||
export const CyclesList: FC<ICyclesList> = observer((props) => {
|
||||
const { cycles, filter, workspaceSlug, projectId } = props;
|
||||
|
||||
// store hooks
|
||||
const {
|
||||
commandPalette: commandPaletteStore,
|
||||
trackEvent: { setTrackElement },
|
||||
} = useMobxStore();
|
||||
eventTracker: { setTrackElement },
|
||||
} = useApplication();
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -1,19 +1,18 @@
|
||||
import { FC } from "react";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { KeyedMutator } from "swr";
|
||||
|
||||
// hooks
|
||||
import { useUser } from "hooks/store";
|
||||
// services
|
||||
import { CycleService } from "services/cycle.service";
|
||||
// hooks
|
||||
import useUser from "hooks/use-user";
|
||||
import useProjectDetails from "hooks/use-project-details";
|
||||
// components
|
||||
import { GanttChartRoot, IBlockUpdateData, CycleGanttSidebar } from "components/gantt-chart";
|
||||
import { CycleGanttBlock } from "components/cycles";
|
||||
// types
|
||||
import { ICycle } from "types";
|
||||
// constants
|
||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
|
||||
type Props = {
|
||||
workspaceSlug: string;
|
||||
@ -24,15 +23,18 @@ type Props = {
|
||||
// services
|
||||
const cycleService = new CycleService();
|
||||
|
||||
export const CyclesListGanttChartView: FC<Props> = ({ cycles, mutateCycles }) => {
|
||||
export const CyclesListGanttChartView: FC<Props> = observer((props) => {
|
||||
const { cycles, mutateCycles } = props;
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const { user } = useUser();
|
||||
const { projectDetails } = useProjectDetails();
|
||||
// store hooks
|
||||
const {
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
|
||||
const handleCycleUpdate = (cycle: ICycle, payload: IBlockUpdateData) => {
|
||||
if (!workspaceSlug || !user) return;
|
||||
if (!workspaceSlug) return;
|
||||
mutateCycles &&
|
||||
mutateCycles((prevData: any) => {
|
||||
if (!prevData) return prevData;
|
||||
@ -76,7 +78,8 @@ export const CyclesListGanttChartView: FC<Props> = ({ cycles, mutateCycles }) =>
|
||||
}))
|
||||
: [];
|
||||
|
||||
const isAllowed = projectDetails?.member_role === 20 || projectDetails?.member_role === 15;
|
||||
const isAllowed =
|
||||
currentProjectRole && [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER].includes(currentProjectRole);
|
||||
|
||||
return (
|
||||
<div className="h-full w-full overflow-y-auto">
|
||||
@ -94,4 +97,4 @@ export const CyclesListGanttChartView: FC<Props> = ({ cycles, mutateCycles }) =>
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -30,6 +30,8 @@ import {
|
||||
} from "helpers/date-time.helper";
|
||||
// types
|
||||
import { ICycle } from "types";
|
||||
// constants
|
||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
// fetch-keys
|
||||
import { CYCLE_STATUS } from "constants/cycle";
|
||||
|
||||
@ -53,6 +55,7 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
const {
|
||||
cycle: cycleDetailsStore,
|
||||
trackEvent: { setTrackElement },
|
||||
user: { currentProjectRole },
|
||||
} = useMobxStore();
|
||||
|
||||
const cycleDetails = cycleDetailsStore.cycle_details[cycleId] ?? undefined;
|
||||
@ -270,10 +273,11 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
</Loader>
|
||||
);
|
||||
|
||||
const endDate = new Date(cycleDetails.end_date ?? "");
|
||||
const startDate = new Date(cycleDetails.start_date ?? "");
|
||||
const endDate = new Date(watch("end_date") ?? cycleDetails.end_date ?? "");
|
||||
const startDate = new Date(watch("start_date") ?? cycleDetails.start_date ?? "");
|
||||
|
||||
const areYearsEqual = startDate.getFullYear() === endDate.getFullYear();
|
||||
const areYearsEqual =
|
||||
startDate.getFullYear() === endDate.getFullYear() || isNaN(startDate.getFullYear()) || isNaN(endDate.getFullYear());
|
||||
|
||||
const currentCycle = CYCLE_STATUS.find((status) => status.value === cycleStatus);
|
||||
|
||||
@ -286,6 +290,8 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
: `${cycleDetails.total_issues}`
|
||||
: `${cycleDetails.completed_issues}/${cycleDetails.total_issues}`;
|
||||
|
||||
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
|
||||
|
||||
return (
|
||||
<>
|
||||
{cycleDetails && workspaceSlug && projectId && (
|
||||
@ -312,7 +318,7 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
<button onClick={handleCopyText}>
|
||||
<LinkIcon className="h-3 w-3 text-custom-text-300" />
|
||||
</button>
|
||||
{!isCompleted && (
|
||||
{!isCompleted && isEditingAllowed && (
|
||||
<CustomMenu width="lg" placement="bottom-end" ellipsis>
|
||||
<CustomMenu.MenuItem
|
||||
onClick={() => {
|
||||
@ -349,8 +355,10 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
<div className="relative flex h-full w-52 items-center gap-2.5">
|
||||
<Popover className="flex h-full items-center justify-center rounded-lg">
|
||||
<Popover.Button
|
||||
disabled={isCompleted ?? false}
|
||||
className="cursor-default text-sm font-medium text-custom-text-300"
|
||||
className={`text-sm font-medium text-custom-text-300 ${
|
||||
isEditingAllowed ? "cursor-pointer" : "cursor-not-allowed"
|
||||
}`}
|
||||
disabled={isCompleted || !isEditingAllowed}
|
||||
>
|
||||
{areYearsEqual ? renderShortDate(startDate, "_ _") : renderShortMonthDate(startDate, "_ _")}
|
||||
</Popover.Button>
|
||||
@ -373,10 +381,10 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
handleStartDateChange(val);
|
||||
}
|
||||
}}
|
||||
startDate={watch("start_date") ? `${watch("start_date")}` : null}
|
||||
endDate={watch("end_date") ? `${watch("end_date")}` : null}
|
||||
startDate={watch("start_date") ?? watch("end_date") ?? null}
|
||||
endDate={watch("end_date") ?? watch("start_date") ?? null}
|
||||
maxDate={new Date(`${watch("end_date")}`)}
|
||||
selectsStart
|
||||
selectsStart={watch("end_date") ? true : false}
|
||||
/>
|
||||
</Popover.Panel>
|
||||
</Transition>
|
||||
@ -385,8 +393,10 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
<Popover className="flex h-full items-center justify-center rounded-lg">
|
||||
<>
|
||||
<Popover.Button
|
||||
disabled={isCompleted ?? false}
|
||||
className="cursor-default text-sm font-medium text-custom-text-300"
|
||||
className={`text-sm font-medium text-custom-text-300 ${
|
||||
isEditingAllowed ? "cursor-pointer" : "cursor-not-allowed"
|
||||
}`}
|
||||
disabled={isCompleted || !isEditingAllowed}
|
||||
>
|
||||
{areYearsEqual ? renderShortDate(endDate, "_ _") : renderShortMonthDate(endDate, "_ _")}
|
||||
</Popover.Button>
|
||||
@ -409,10 +419,10 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
handleEndDateChange(val);
|
||||
}
|
||||
}}
|
||||
startDate={watch("start_date") ? `${watch("start_date")}` : null}
|
||||
endDate={watch("end_date") ? `${watch("end_date")}` : null}
|
||||
startDate={watch("start_date") ?? watch("end_date") ?? null}
|
||||
endDate={watch("end_date") ?? watch("start_date") ?? null}
|
||||
minDate={new Date(`${watch("start_date")}`)}
|
||||
selectsEnd
|
||||
selectsEnd={watch("start_date") ? true : false}
|
||||
/>
|
||||
</Popover.Panel>
|
||||
</Transition>
|
||||
|
@ -1,11 +1,8 @@
|
||||
import React from "react";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
// store
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useProject } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
// ui
|
||||
import { Button, CustomMenu } from "@plane/ui";
|
||||
@ -27,10 +24,8 @@ export const EstimateListItem: React.FC<Props> = observer((props) => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
// store
|
||||
const {
|
||||
project: { currentProjectDetails, updateProject },
|
||||
} = useMobxStore();
|
||||
// store hooks
|
||||
const { currentProjectDetails, updateProject } = useProject();
|
||||
// hooks
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
|
@ -2,8 +2,8 @@ import React, { useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useProject } from "hooks/store";
|
||||
// services
|
||||
import { ProjectExportService } from "services/project";
|
||||
// hooks
|
||||
@ -26,28 +26,30 @@ const projectExportService = new ProjectExportService();
|
||||
|
||||
export const Exporter: React.FC<Props> = observer((props) => {
|
||||
const { isOpen, handleClose, user, provider, mutateServices } = props;
|
||||
|
||||
// states
|
||||
const [exportLoading, setExportLoading] = useState(false);
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const { project: projectStore } = useMobxStore();
|
||||
|
||||
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined;
|
||||
|
||||
// store hooks
|
||||
const { workspaceProjects, getProjectById } = useProject();
|
||||
// toast alert
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const options = projects?.map((project) => ({
|
||||
value: project.id,
|
||||
query: project.name + project.identifier,
|
||||
content: (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[0.65rem] text-custom-text-200">{project.identifier}</span>
|
||||
{project.name}
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
const options = workspaceProjects?.map((projectId) => {
|
||||
const projectDetails = getProjectById(projectId);
|
||||
|
||||
return {
|
||||
value: projectDetails?.id,
|
||||
query: `${projectDetails?.name} ${projectDetails?.identifier}`,
|
||||
content: (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[0.65rem] text-custom-text-200">{projectDetails?.identifier}</span>
|
||||
{projectDetails?.name}
|
||||
</div>
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
const [value, setValue] = React.useState<string[]>([]);
|
||||
const [multiple, setMultiple] = React.useState<boolean>(false);
|
||||
@ -131,10 +133,12 @@ export const Exporter: React.FC<Props> = observer((props) => {
|
||||
input
|
||||
label={
|
||||
value && value.length > 0
|
||||
? projects &&
|
||||
projects
|
||||
.filter((p) => value.includes(p.id))
|
||||
.map((p) => p.identifier)
|
||||
? value
|
||||
.map((projectId) => {
|
||||
const projectDetails = getProjectById(projectId);
|
||||
|
||||
return projectDetails?.identifier;
|
||||
})
|
||||
.join(", ")
|
||||
: "All projects"
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ import { IGanttBlock, IBlockUpdateData } from "components/gantt-chart/types";
|
||||
import { IIssue } from "types";
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
||||
blocks: IGanttBlock[] | null;
|
||||
enableReorder: boolean;
|
||||
@ -33,7 +32,6 @@ type Props = {
|
||||
export const IssueGanttSidebar: React.FC<Props> = (props) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const {
|
||||
title,
|
||||
blockUpdateHandler,
|
||||
blocks,
|
||||
enableReorder,
|
||||
|
@ -156,7 +156,10 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
||||
key={cycle.id}
|
||||
onClick={() => router.push(`/${workspaceSlug}/projects/${projectId}/cycles/${cycle.id}`)}
|
||||
>
|
||||
{truncateText(cycle.name, 40)}
|
||||
<div className="flex items-center gap-1.5">
|
||||
<ContrastIcon className="h-3 w-3" />
|
||||
{truncateText(cycle.name, 40)}
|
||||
</div>
|
||||
</CustomMenu.MenuItem>
|
||||
))}
|
||||
</CustomMenu>
|
||||
@ -193,20 +196,23 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
||||
handleDisplayPropertiesUpdate={handleDisplayProperties}
|
||||
/>
|
||||
</FiltersDropdown>
|
||||
<Button onClick={() => setAnalyticsModal(true)} variant="neutral-primary" size="sm">
|
||||
Analytics
|
||||
</Button>
|
||||
|
||||
{canUserCreateIssue && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
setTrackElement("CYCLE_PAGE_HEADER");
|
||||
commandPaletteStore.toggleCreateIssueModal(true, EProjectStore.CYCLE);
|
||||
}}
|
||||
size="sm"
|
||||
prependIcon={<Plus />}
|
||||
>
|
||||
Add Issue
|
||||
</Button>
|
||||
<>
|
||||
<Button onClick={() => setAnalyticsModal(true)} variant="neutral-primary" size="sm">
|
||||
Analytics
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setTrackElement("CYCLE_PAGE_HEADER");
|
||||
commandPaletteStore.toggleCreateIssueModal(true, EProjectStore.CYCLE);
|
||||
}}
|
||||
size="sm"
|
||||
prependIcon={<Plus />}
|
||||
>
|
||||
Add Issue
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
|
@ -2,8 +2,8 @@ import { FC } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Plus } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication, useProject, useUser } from "hooks/store";
|
||||
// ui
|
||||
import { Breadcrumbs, Button, ContrastIcon } from "@plane/ui";
|
||||
// helpers
|
||||
@ -14,14 +14,15 @@ export const CyclesHeader: FC = observer(() => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
// store
|
||||
// store hooks
|
||||
const {
|
||||
project: projectStore,
|
||||
user: { currentProjectRole },
|
||||
commandPalette: commandPaletteStore,
|
||||
trackEvent: { setTrackElement },
|
||||
} = useMobxStore();
|
||||
const { currentProjectDetails } = projectStore;
|
||||
commandPalette: { toggleCreateCycleModal },
|
||||
eventTracker: { setTrackElement },
|
||||
} = useApplication();
|
||||
const {
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
const { currentProjectDetails } = useProject();
|
||||
|
||||
const canUserCreateCycle =
|
||||
currentProjectRole && [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER].includes(currentProjectRole);
|
||||
@ -63,7 +64,7 @@ export const CyclesHeader: FC = observer(() => {
|
||||
prependIcon={<Plus />}
|
||||
onClick={() => {
|
||||
setTrackElement("CYCLES_PAGE_HEADER");
|
||||
commandPaletteStore.toggleCreateCycleModal(true);
|
||||
toggleCreateCycleModal(true);
|
||||
}}
|
||||
>
|
||||
Add Cycle
|
||||
|
@ -16,6 +16,7 @@ import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOption
|
||||
// constants
|
||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
||||
import { EFilterType } from "store_legacy/issues/types";
|
||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
|
||||
const GLOBAL_VIEW_LAYOUTS = [
|
||||
{ key: "list", title: "List", link: "/workspace-views", icon: List },
|
||||
@ -38,7 +39,7 @@ export const GlobalIssuesHeader: React.FC<Props> = observer((props) => {
|
||||
workspace: { workspaceLabels },
|
||||
workspaceMember: { workspaceMembers },
|
||||
project: { workspaceProjects },
|
||||
|
||||
user: { currentWorkspaceRole },
|
||||
workspaceGlobalIssuesFilter: { issueFilters, updateFilters },
|
||||
} = useMobxStore();
|
||||
|
||||
@ -77,6 +78,8 @@ export const GlobalIssuesHeader: React.FC<Props> = observer((props) => {
|
||||
[workspaceSlug, updateFilters]
|
||||
);
|
||||
|
||||
const isAuthorizedUser = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER;
|
||||
|
||||
return (
|
||||
<>
|
||||
<CreateUpdateWorkspaceViewModal isOpen={createViewModal} onClose={() => setCreateViewModal(false)} />
|
||||
@ -142,10 +145,11 @@ export const GlobalIssuesHeader: React.FC<Props> = observer((props) => {
|
||||
</FiltersDropdown>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Button variant="primary" size="sm" prependIcon={<PlusIcon />} onClick={() => setCreateViewModal(true)}>
|
||||
New View
|
||||
</Button>
|
||||
{isAuthorizedUser && (
|
||||
<Button variant="primary" size="sm" prependIcon={<PlusIcon />} onClick={() => setCreateViewModal(true)}>
|
||||
New View
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
@ -11,7 +11,7 @@ import { ProjectAnalyticsModal } from "components/analytics";
|
||||
// ui
|
||||
import { Breadcrumbs, Button, CustomMenu, DiceIcon } from "@plane/ui";
|
||||
// icons
|
||||
import { ArrowRight, ContrastIcon, Plus } from "lucide-react";
|
||||
import { ArrowRight, Plus } from "lucide-react";
|
||||
// helpers
|
||||
import { truncateText } from "helpers/string.helper";
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
@ -144,7 +144,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
||||
<CustomMenu
|
||||
label={
|
||||
<>
|
||||
<ContrastIcon className="h-3 w-3" />
|
||||
<DiceIcon className="h-3 w-3" />
|
||||
{moduleDetails?.name && truncateText(moduleDetails.name, 40)}
|
||||
</>
|
||||
}
|
||||
@ -157,7 +157,10 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
||||
key={module.id}
|
||||
onClick={() => router.push(`/${workspaceSlug}/projects/${projectId}/modules/${module.id}`)}
|
||||
>
|
||||
{truncateText(module.name, 40)}
|
||||
<div className="flex items-center gap-1.5">
|
||||
<DiceIcon className="h-3 w-3" />
|
||||
{truncateText(module.name, 40)}
|
||||
</div>
|
||||
</CustomMenu.MenuItem>
|
||||
))}
|
||||
</CustomMenu>
|
||||
@ -194,20 +197,23 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
||||
handleDisplayPropertiesUpdate={handleDisplayProperties}
|
||||
/>
|
||||
</FiltersDropdown>
|
||||
<Button onClick={() => setAnalyticsModal(true)} variant="neutral-primary" size="sm">
|
||||
Analytics
|
||||
</Button>
|
||||
|
||||
{canUserCreateIssue && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
setTrackElement("MODULE_PAGE_HEADER");
|
||||
commandPaletteStore.toggleCreateIssueModal(true, EProjectStore.MODULE);
|
||||
}}
|
||||
size="sm"
|
||||
prependIcon={<Plus />}
|
||||
>
|
||||
Add Issue
|
||||
</Button>
|
||||
<>
|
||||
<Button onClick={() => setAnalyticsModal(true)} variant="neutral-primary" size="sm">
|
||||
Analytics
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setTrackElement("MODULE_PAGE_HEADER");
|
||||
commandPaletteStore.toggleCreateIssueModal(true, EProjectStore.MODULE);
|
||||
}}
|
||||
size="sm"
|
||||
prependIcon={<Plus />}
|
||||
>
|
||||
Add Issue
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Plus } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication, useProject, useUser } from "hooks/store";
|
||||
import useLocalStorage from "hooks/use-local-storage";
|
||||
// ui
|
||||
import { Breadcrumbs, Button, Tooltip, DiceIcon } from "@plane/ui";
|
||||
@ -17,13 +16,12 @@ export const ModulesListHeader: React.FC = observer(() => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
// store
|
||||
// store hooks
|
||||
const { commandPalette: commandPaletteStore } = useApplication();
|
||||
const {
|
||||
project: projectStore,
|
||||
commandPalette: commandPaletteStore,
|
||||
user: { currentProjectRole },
|
||||
} = useMobxStore();
|
||||
const { currentProjectDetails } = projectStore;
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
const { currentProjectDetails } = useProject();
|
||||
|
||||
const { storedValue: modulesView, setValue: setModulesView } = useLocalStorage("modules_view", "grid");
|
||||
|
||||
|
@ -1,21 +1,18 @@
|
||||
import { FC } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import useSWR from "swr";
|
||||
import { FileText, Plus } from "lucide-react";
|
||||
// hooks
|
||||
import { useApplication, useProject } from "hooks/store";
|
||||
// services
|
||||
import { PageService } from "services/page.service";
|
||||
|
||||
// constants
|
||||
import { PAGE_DETAILS } from "constants/fetch-keys";
|
||||
|
||||
// hooks
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// ui
|
||||
import { Breadcrumbs, Button } from "@plane/ui";
|
||||
// helper
|
||||
// helpers
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
|
||||
import useSWR from "swr";
|
||||
// fetch-keys
|
||||
import { PAGE_DETAILS } from "constants/fetch-keys";
|
||||
|
||||
export interface IPagesHeaderProps {
|
||||
showButton?: boolean;
|
||||
@ -28,8 +25,8 @@ export const PageDetailsHeader: FC<IPagesHeaderProps> = observer((props) => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, pageId } = router.query;
|
||||
|
||||
const { project: projectStore, commandPalette: commandPaletteStore } = useMobxStore();
|
||||
const { currentProjectDetails } = projectStore;
|
||||
const { commandPalette: commandPaletteStore } = useApplication();
|
||||
const { currentProjectDetails } = useProject();
|
||||
|
||||
const { data: pageDetails } = useSWR(
|
||||
workspaceSlug && currentProjectDetails?.id && pageId ? PAGE_DETAILS(pageId as string) : null,
|
||||
|
@ -2,10 +2,10 @@ import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { FileText, Plus } from "lucide-react";
|
||||
// hooks
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { useApplication, useProject, useUser } from "hooks/store";
|
||||
// ui
|
||||
import { Breadcrumbs, Button } from "@plane/ui";
|
||||
// helper
|
||||
// helpers
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
// constants
|
||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
@ -14,12 +14,14 @@ export const PagesHeader = observer(() => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
// mobx store
|
||||
// store hooks
|
||||
const {
|
||||
user: { currentProjectRole },
|
||||
project: { currentProjectDetails },
|
||||
commandPalette: { toggleCreatePageModal },
|
||||
} = useMobxStore();
|
||||
} = useApplication();
|
||||
const {
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
const { currentProjectDetails } = useProject();
|
||||
|
||||
const canUserCreatePage =
|
||||
currentProjectRole && [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER].includes(currentProjectRole);
|
||||
|
@ -3,7 +3,7 @@ import useSWR from "swr";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { useProject } from "hooks/store";
|
||||
// ui
|
||||
import { Breadcrumbs, LayersIcon } from "@plane/ui";
|
||||
// types
|
||||
@ -18,12 +18,11 @@ import { renderEmoji } from "helpers/emoji.helper";
|
||||
const issueArchiveService = new IssueArchiveService();
|
||||
|
||||
export const ProjectArchivedIssueDetailsHeader: FC = observer(() => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, archivedIssueId } = router.query;
|
||||
|
||||
const { project: projectStore } = useMobxStore();
|
||||
|
||||
const { currentProjectDetails } = projectStore;
|
||||
// store hooks
|
||||
const { currentProjectDetails } = useProject();
|
||||
|
||||
const { data: issueDetails } = useSWR<IIssue | undefined>(
|
||||
workspaceSlug && projectId && archivedIssueId ? ISSUE_DETAILS(archivedIssueId as string) : null,
|
||||
|
@ -3,7 +3,7 @@ import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Plus } from "lucide-react";
|
||||
// hooks
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { useProject } from "hooks/store";
|
||||
// ui
|
||||
import { Breadcrumbs, Button, LayersIcon } from "@plane/ui";
|
||||
// components
|
||||
@ -12,13 +12,13 @@ import { CreateInboxIssueModal } from "components/inbox";
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
|
||||
export const ProjectInboxHeader: FC = observer(() => {
|
||||
// states
|
||||
const [createIssueModal, setCreateIssueModal] = useState(false);
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
const [createIssueModal, setCreateIssueModal] = useState(false);
|
||||
|
||||
const { project: projectStore } = useMobxStore();
|
||||
|
||||
const { currentProjectDetails } = projectStore;
|
||||
// store hooks
|
||||
const { currentProjectDetails } = useProject();
|
||||
|
||||
return (
|
||||
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
|
||||
|
@ -2,27 +2,26 @@ import { FC } from "react";
|
||||
import useSWR from "swr";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
// hooks
|
||||
import { useProject } from "hooks/store";
|
||||
// ui
|
||||
import { Breadcrumbs, LayersIcon } from "@plane/ui";
|
||||
// helper
|
||||
// helpers
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
// services
|
||||
import { IssueService } from "services/issue";
|
||||
// constants
|
||||
import { ISSUE_DETAILS } from "constants/fetch-keys";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
// services
|
||||
const issueService = new IssueService();
|
||||
|
||||
export const ProjectIssueDetailsHeader: FC = observer(() => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, issueId } = router.query;
|
||||
|
||||
const { project: projectStore } = useMobxStore();
|
||||
|
||||
const { currentProjectDetails } = projectStore;
|
||||
// store hooks
|
||||
const { currentProjectDetails } = useProject();
|
||||
|
||||
const { data: issueDetails } = useSWR(
|
||||
workspaceSlug && projectId && issueId ? ISSUE_DETAILS(issueId as string) : null,
|
||||
|
@ -202,20 +202,23 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
|
||||
</span>
|
||||
</Link>
|
||||
)}
|
||||
<Button onClick={() => setAnalyticsModal(true)} variant="neutral-primary" size="sm">
|
||||
Analytics
|
||||
</Button>
|
||||
|
||||
{canUserCreateIssue && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
setTrackElement("PROJECT_PAGE_HEADER");
|
||||
commandPaletteStore.toggleCreateIssueModal(true, EProjectStore.PROJECT);
|
||||
}}
|
||||
size="sm"
|
||||
prependIcon={<Plus />}
|
||||
>
|
||||
Add Issue
|
||||
</Button>
|
||||
<>
|
||||
<Button onClick={() => setAnalyticsModal(true)} variant="neutral-primary" size="sm">
|
||||
Analytics
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setTrackElement("PROJECT_PAGE_HEADER");
|
||||
commandPaletteStore.toggleCreateIssueModal(true, EProjectStore.PROJECT);
|
||||
}}
|
||||
size="sm"
|
||||
prependIcon={<Plus />}
|
||||
>
|
||||
Add Issue
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,13 +1,12 @@
|
||||
import { FC } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import { observer } from "mobx-react-lite";
|
||||
// ui
|
||||
import { Breadcrumbs } from "@plane/ui";
|
||||
// helper
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
// hooks
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useProject, useUser } from "hooks/store";
|
||||
// constants
|
||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
|
||||
@ -17,14 +16,14 @@ export interface IProjectSettingHeader {
|
||||
|
||||
export const ProjectSettingHeader: FC<IProjectSettingHeader> = observer((props) => {
|
||||
const { title } = props;
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
// store
|
||||
// store hooks
|
||||
const {
|
||||
project: projectStore,
|
||||
user: { currentProjectRole },
|
||||
} = useMobxStore();
|
||||
const { currentProjectDetails } = projectStore;
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
const { currentProjectDetails } = useProject();
|
||||
|
||||
if (currentProjectRole && currentProjectRole <= EUserWorkspaceRoles.VIEWER) return null;
|
||||
|
||||
|
@ -139,7 +139,10 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
||||
key={view.id}
|
||||
onClick={() => router.push(`/${workspaceSlug}/projects/${projectId}/views/${view.id}`)}
|
||||
>
|
||||
{truncateText(view.name, 40)}
|
||||
<div className="flex items-center gap-1.5">
|
||||
<PhotoFilterIcon height={12} width={12} />
|
||||
{truncateText(view.name, 40)}
|
||||
</div>
|
||||
</CustomMenu.MenuItem>
|
||||
))}
|
||||
</CustomMenu>
|
||||
@ -153,7 +156,8 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
||||
onChange={(layout) => handleLayoutChange(layout)}
|
||||
selectedLayout={activeLayout}
|
||||
/>
|
||||
<FiltersDropdown title="Filters" placement="bottom-end">
|
||||
|
||||
<FiltersDropdown title="Filters" placement="bottom-end" disabled={!canUserCreateIssue}>
|
||||
<FilterSelection
|
||||
filters={issueFilters?.filters ?? {}}
|
||||
handleFiltersUpdate={handleFiltersUpdate}
|
||||
@ -176,7 +180,7 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
||||
handleDisplayPropertiesUpdate={handleDisplayProperties}
|
||||
/>
|
||||
</FiltersDropdown>
|
||||
{
|
||||
{canUserCreateIssue && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
setTrackElement("PROJECT_VIEW_PAGE_HEADER");
|
||||
@ -187,7 +191,7 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
||||
>
|
||||
Add Issue
|
||||
</Button>
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,20 +1,30 @@
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Plus } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication, useProject, useUser } from "hooks/store";
|
||||
// components
|
||||
import { Breadcrumbs, PhotoFilterIcon, Button } from "@plane/ui";
|
||||
// helpers
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
// constants
|
||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
|
||||
export const ProjectViewsHeader: React.FC = observer(() => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
// store hooks
|
||||
const {
|
||||
commandPalette: { toggleCreateViewModal },
|
||||
} = useApplication();
|
||||
const {
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
const { currentProjectDetails } = useProject();
|
||||
|
||||
const { project: projectStore, commandPalette } = useMobxStore();
|
||||
const { currentProjectDetails } = projectStore;
|
||||
const canUserCreateIssue =
|
||||
currentProjectRole && [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER].includes(currentProjectRole);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -50,18 +60,20 @@ export const ProjectViewsHeader: React.FC = observer(() => {
|
||||
</Breadcrumbs>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-shrink-0 items-center gap-2">
|
||||
<div>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
prependIcon={<Plus className="h-3.5 w-3.5 stroke-2" />}
|
||||
onClick={() => commandPalette.toggleCreateViewModal(true)}
|
||||
>
|
||||
Create View
|
||||
</Button>
|
||||
{canUserCreateIssue && (
|
||||
<div className="flex flex-shrink-0 items-center gap-2">
|
||||
<div>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
prependIcon={<Plus className="h-3.5 w-3.5 stroke-2" />}
|
||||
onClick={() => toggleCreateViewModal(true)}
|
||||
>
|
||||
Create View
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
@ -1,23 +1,24 @@
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Search, Plus, Briefcase } from "lucide-react";
|
||||
// hooks
|
||||
import { useApplication, useProject, useUser } from "hooks/store";
|
||||
// ui
|
||||
import { Breadcrumbs, Button } from "@plane/ui";
|
||||
// hooks
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// constants
|
||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
|
||||
export const ProjectsHeader = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
// store
|
||||
// store hooks
|
||||
const {
|
||||
project: projectStore,
|
||||
commandPalette: commandPaletteStore,
|
||||
trackEvent: { setTrackElement },
|
||||
} = useMobxStore();
|
||||
eventTracker: { setTrackElement },
|
||||
} = useApplication();
|
||||
const {
|
||||
membership: { currentWorkspaceRole },
|
||||
} = useUser();
|
||||
const { workspaceProjects, searchQuery, setSearchQuery } = useProject();
|
||||
|
||||
const projectsList = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : [];
|
||||
const isAuthorizedUser = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER;
|
||||
|
||||
return (
|
||||
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
|
||||
@ -33,28 +34,29 @@ export const ProjectsHeader = observer(() => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
{projectsList?.length > 0 && (
|
||||
{workspaceProjects && workspaceProjects?.length > 0 && (
|
||||
<div className="flex w-full items-center justify-start gap-1 rounded-md border border-custom-border-200 bg-custom-background-100 px-2.5 py-1.5 text-custom-text-400">
|
||||
<Search className="h-3.5 w-3.5" />
|
||||
<input
|
||||
className="w-full min-w-[234px] border-none bg-transparent text-sm focus:outline-none"
|
||||
value={projectStore.searchQuery}
|
||||
onChange={(e) => projectStore.setSearchQuery(e.target.value)}
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
placeholder="Search"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Button
|
||||
prependIcon={<Plus />}
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setTrackElement("PROJECTS_PAGE_HEADER");
|
||||
commandPaletteStore.toggleCreateProjectModal(true);
|
||||
}}
|
||||
>
|
||||
Add Project
|
||||
</Button>
|
||||
{isAuthorizedUser && (
|
||||
<Button
|
||||
prependIcon={<Plus />}
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setTrackElement("PROJECTS_PAGE_HEADER");
|
||||
commandPaletteStore.toggleCreateProjectModal(true);
|
||||
}}
|
||||
>
|
||||
Add Project
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { useRouter } from "next/router";
|
||||
import useSWR, { mutate } from "swr";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication, useUser, useWorkspace } from "hooks/store";
|
||||
// components
|
||||
import { AddComment, IssueActivitySection } from "components/issues";
|
||||
// services
|
||||
@ -21,14 +21,15 @@ const issueService = new IssueService();
|
||||
const issueCommentService = new IssueCommentService();
|
||||
|
||||
export const InboxIssueActivity: React.FC<Props> = observer(({ issueDetails }) => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
|
||||
// store hooks
|
||||
const {
|
||||
user: userStore,
|
||||
trackEvent: { postHogEventTracker },
|
||||
workspace: { currentWorkspace },
|
||||
} = useMobxStore();
|
||||
eventTracker: { postHogEventTracker },
|
||||
} = useApplication();
|
||||
const { currentUser } = useUser();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
@ -39,13 +40,11 @@ export const InboxIssueActivity: React.FC<Props> = observer(({ issueDetails }) =
|
||||
: null
|
||||
);
|
||||
|
||||
const user = userStore.currentUser;
|
||||
|
||||
const handleCommentUpdate = async (commentId: string, data: Partial<any>) => {
|
||||
if (!workspaceSlug || !projectId || !issueDetails.id || !user) return;
|
||||
if (!workspaceSlug || !projectId || !issueDetails.id || !currentUser) return;
|
||||
|
||||
await issueCommentService
|
||||
.patchIssueComment(workspaceSlug as string, projectId as string, issueDetails.id as string, commentId, data)
|
||||
.patchIssueComment(workspaceSlug.toString(), projectId.toString(), issueDetails.id, commentId, data)
|
||||
.then((res) => {
|
||||
mutateIssueActivity();
|
||||
postHogEventTracker(
|
||||
@ -57,19 +56,19 @@ export const InboxIssueActivity: React.FC<Props> = observer(({ issueDetails }) =
|
||||
{
|
||||
isGrouping: true,
|
||||
groupType: "Workspace_metrics",
|
||||
gorupId: currentWorkspace?.id!,
|
||||
groupId: currentWorkspace?.id!,
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const handleCommentDelete = async (commentId: string) => {
|
||||
if (!workspaceSlug || !projectId || !issueDetails.id || !user) return;
|
||||
if (!workspaceSlug || !projectId || !issueDetails.id || !currentUser) return;
|
||||
|
||||
mutateIssueActivity((prevData: any) => prevData?.filter((p: any) => p.id !== commentId), false);
|
||||
|
||||
await issueCommentService
|
||||
.deleteIssueComment(workspaceSlug as string, projectId as string, issueDetails.id as string, commentId)
|
||||
.deleteIssueComment(workspaceSlug.toString(), projectId.toString(), issueDetails.id, commentId)
|
||||
.then(() => {
|
||||
mutateIssueActivity();
|
||||
postHogEventTracker(
|
||||
@ -80,14 +79,14 @@ export const InboxIssueActivity: React.FC<Props> = observer(({ issueDetails }) =
|
||||
{
|
||||
isGrouping: true,
|
||||
groupType: "Workspace_metrics",
|
||||
gorupId: currentWorkspace?.id!,
|
||||
groupId: currentWorkspace?.id!,
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const handleAddComment = async (formData: IIssueActivity) => {
|
||||
if (!workspaceSlug || !issueDetails || !user) return;
|
||||
if (!workspaceSlug || !issueDetails || !currentUser) return;
|
||||
|
||||
await issueCommentService
|
||||
.createIssueComment(workspaceSlug.toString(), issueDetails.project, issueDetails.id, formData)
|
||||
@ -102,7 +101,7 @@ export const InboxIssueActivity: React.FC<Props> = observer(({ issueDetails }) =
|
||||
{
|
||||
isGrouping: true,
|
||||
groupType: "Workspace_metrics",
|
||||
gorupId: currentWorkspace?.id!,
|
||||
groupId: currentWorkspace?.id!,
|
||||
}
|
||||
);
|
||||
})
|
||||
|
@ -165,16 +165,16 @@ export const InboxMainContent: React.FC = observer(() => {
|
||||
issueStatus === -2
|
||||
? "border-yellow-500 bg-yellow-500/10 text-yellow-500"
|
||||
: issueStatus === -1
|
||||
? "border-red-500 bg-red-500/10 text-red-500"
|
||||
: issueStatus === 0
|
||||
? new Date(issueDetails.issue_inbox[0].snoozed_till ?? "") < new Date()
|
||||
? "border-red-500 bg-red-500/10 text-red-500"
|
||||
: issueStatus === 0
|
||||
? new Date(issueDetails.issue_inbox[0].snoozed_till ?? "") < new Date()
|
||||
? "border-red-500 bg-red-500/10 text-red-500"
|
||||
: "border-gray-500 bg-gray-500/10 text-custom-text-200"
|
||||
: issueStatus === 1
|
||||
? "border-green-500 bg-green-500/10 text-green-500"
|
||||
: issueStatus === 2
|
||||
? "border-gray-500 bg-gray-500/10 text-custom-text-200"
|
||||
: ""
|
||||
: "border-gray-500 bg-gray-500/10 text-custom-text-200"
|
||||
: issueStatus === 1
|
||||
? "border-green-500 bg-green-500/10 text-green-500"
|
||||
: issueStatus === 2
|
||||
? "border-gray-500 bg-gray-500/10 text-custom-text-200"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
{issueStatus === -2 ? (
|
||||
@ -225,7 +225,7 @@ export const InboxMainContent: React.FC = observer(() => {
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="mb-5 flex items-center">
|
||||
<div className="mb-2.5 flex items-center">
|
||||
{currentIssueState && (
|
||||
<StateGroupIcon
|
||||
className="mr-3 h-4 w-4"
|
||||
|
@ -1,15 +1,13 @@
|
||||
import { FC, useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { Eye, EyeOff } from "lucide-react";
|
||||
// ui
|
||||
import { Button, Input } from "@plane/ui";
|
||||
// types
|
||||
import { IFormattedInstanceConfiguration } from "types/instance";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// icons
|
||||
import { Eye, EyeOff } from "lucide-react";
|
||||
|
||||
export interface IInstanceAIForm {
|
||||
config: IFormattedInstanceConfiguration;
|
||||
@ -25,7 +23,7 @@ export const InstanceAIForm: FC<IInstanceAIForm> = (props) => {
|
||||
// states
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
// store
|
||||
const { instance: instanceStore } = useMobxStore();
|
||||
const { instance: instanceStore } = useApplication();
|
||||
// toast
|
||||
const { setToastAlert } = useToast();
|
||||
// form data
|
||||
|
@ -6,9 +6,8 @@ import { Eye, EyeOff } from "lucide-react";
|
||||
// types
|
||||
import { IFormattedInstanceConfiguration } from "types/instance";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
export interface IInstanceEmailForm {
|
||||
config: IFormattedInstanceConfiguration;
|
||||
@ -27,8 +26,8 @@ export const InstanceEmailForm: FC<IInstanceEmailForm> = (props) => {
|
||||
const { config } = props;
|
||||
// states
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
// store
|
||||
const { instance: instanceStore } = useMobxStore();
|
||||
// store hooks
|
||||
const { instance: instanceStore } = useApplication();
|
||||
// toast
|
||||
const { setToastAlert } = useToast();
|
||||
// form data
|
||||
|
@ -5,8 +5,8 @@ import { Button, Input } from "@plane/ui";
|
||||
// types
|
||||
import { IInstance, IInstanceAdmin } from "types/instance";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
export interface IInstanceGeneralForm {
|
||||
instance: IInstance;
|
||||
@ -20,8 +20,8 @@ export interface GeneralFormValues {
|
||||
|
||||
export const InstanceGeneralForm: FC<IInstanceGeneralForm> = (props) => {
|
||||
const { instance, instanceAdmins } = props;
|
||||
// store
|
||||
const { instance: instanceStore } = useMobxStore();
|
||||
// store hooks
|
||||
const { instance: instanceStore } = useApplication();
|
||||
// toast
|
||||
const { setToastAlert } = useToast();
|
||||
// form data
|
||||
|
@ -1,15 +1,13 @@
|
||||
import { FC, useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { Copy, Eye, EyeOff } from "lucide-react";
|
||||
// ui
|
||||
import { Button, Input } from "@plane/ui";
|
||||
// types
|
||||
import { IFormattedInstanceConfiguration } from "types/instance";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// icons
|
||||
import { Copy, Eye, EyeOff } from "lucide-react";
|
||||
|
||||
export interface IInstanceGithubConfigForm {
|
||||
config: IFormattedInstanceConfiguration;
|
||||
@ -24,8 +22,8 @@ export const InstanceGithubConfigForm: FC<IInstanceGithubConfigForm> = (props) =
|
||||
const { config } = props;
|
||||
// states
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
// store
|
||||
const { instance: instanceStore } = useMobxStore();
|
||||
// store hooks
|
||||
const { instance: instanceStore } = useApplication();
|
||||
// toast
|
||||
const { setToastAlert } = useToast();
|
||||
// form data
|
||||
|
@ -1,15 +1,13 @@
|
||||
import { FC } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { Copy } from "lucide-react";
|
||||
// ui
|
||||
import { Button, Input } from "@plane/ui";
|
||||
// types
|
||||
import { IFormattedInstanceConfiguration } from "types/instance";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// icons
|
||||
import { Copy } from "lucide-react";
|
||||
|
||||
export interface IInstanceGoogleConfigForm {
|
||||
config: IFormattedInstanceConfiguration;
|
||||
@ -22,8 +20,8 @@ export interface GoogleConfigFormValues {
|
||||
|
||||
export const InstanceGoogleConfigForm: FC<IInstanceGoogleConfigForm> = (props) => {
|
||||
const { config } = props;
|
||||
// store
|
||||
const { instance: instanceStore } = useMobxStore();
|
||||
// store hooks
|
||||
const { instance: instanceStore } = useApplication();
|
||||
// toast
|
||||
const { setToastAlert } = useToast();
|
||||
// form data
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { FC, useState, useRef } from "react";
|
||||
import { Transition } from "@headlessui/react";
|
||||
import Link from "next/link";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// icons
|
||||
import { FileText, HelpCircle, MessagesSquare, MoveLeft } from "lucide-react";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
// icons
|
||||
import { DiscordIcon, GithubIcon } from "@plane/ui";
|
||||
// assets
|
||||
import packageJson from "package.json";
|
||||
@ -39,7 +39,7 @@ export const InstanceHelpSection: FC = () => {
|
||||
// store
|
||||
const {
|
||||
theme: { sidebarCollapsed, toggleSidebar },
|
||||
} = useMobxStore();
|
||||
} = useApplication();
|
||||
// refs
|
||||
const helpOptionsRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
|
@ -1,15 +1,13 @@
|
||||
import { FC, useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { Eye, EyeOff } from "lucide-react";
|
||||
// ui
|
||||
import { Button, Input } from "@plane/ui";
|
||||
// types
|
||||
import { IFormattedInstanceConfiguration } from "types/instance";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// icons
|
||||
import { Eye, EyeOff } from "lucide-react";
|
||||
|
||||
export interface IInstanceImageConfigForm {
|
||||
config: IFormattedInstanceConfiguration;
|
||||
@ -23,8 +21,8 @@ export const InstanceImageConfigForm: FC<IInstanceImageConfigForm> = (props) =>
|
||||
const { config } = props;
|
||||
// states
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
// store
|
||||
const { instance: instanceStore } = useMobxStore();
|
||||
// store hooks
|
||||
const { instance: instanceStore } = useApplication();
|
||||
// toast
|
||||
const { setToastAlert } = useToast();
|
||||
// form data
|
||||
|
@ -1,6 +1,8 @@
|
||||
import React, { useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { useTheme } from "next-themes";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
// ui
|
||||
import { Button } from "@plane/ui";
|
||||
import { UserCog2 } from "lucide-react";
|
||||
@ -8,17 +10,16 @@ import { UserCog2 } from "lucide-react";
|
||||
import instanceSetupDone from "public/instance-setup-done.webp";
|
||||
import PlaneBlackLogo from "public/plane-logos/black-horizontal-with-blue-logo.svg";
|
||||
import PlaneWhiteLogo from "public/plane-logos/white-horizontal-with-blue-logo.svg";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
export const InstanceSetupDone = () => {
|
||||
// states
|
||||
const [isRedirecting, setIsRedirecting] = useState(false);
|
||||
// next-themes
|
||||
const { resolvedTheme } = useTheme();
|
||||
// mobx store
|
||||
// store hooks
|
||||
const {
|
||||
instance: { fetchInstanceInfo },
|
||||
} = useMobxStore();
|
||||
} = useApplication();
|
||||
|
||||
const planeLogo = resolvedTheme === "dark" ? PlaneWhiteLogo : PlaneBlackLogo;
|
||||
|
||||
|
@ -1,11 +1,10 @@
|
||||
import { FC } from "react";
|
||||
import { useForm, Controller } from "react-hook-form";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { XCircle } from "lucide-react";
|
||||
// hooks
|
||||
import { useUser } from "hooks/store";
|
||||
// ui
|
||||
import { Input, Button } from "@plane/ui";
|
||||
// icons
|
||||
import { XCircle } from "lucide-react";
|
||||
// services
|
||||
import { AuthService } from "services/auth.service";
|
||||
const authService = new AuthService();
|
||||
@ -25,9 +24,8 @@ export interface IInstanceSetupEmailForm {
|
||||
|
||||
export const InstanceSetupSignInForm: FC<IInstanceSetupEmailForm> = (props) => {
|
||||
const { handleNextStep } = props;
|
||||
const {
|
||||
user: { fetchCurrentUser },
|
||||
} = useMobxStore();
|
||||
// store hooks
|
||||
const { fetchCurrentUser } = useUser();
|
||||
// form info
|
||||
const {
|
||||
control,
|
||||
|
@ -4,15 +4,13 @@ import Image from "next/image";
|
||||
// components
|
||||
import { InstanceSetupFormRoot } from "components/instance";
|
||||
// hooks
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { useUser } from "hooks/store";
|
||||
// images
|
||||
import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png";
|
||||
|
||||
export const InstanceSetupView = observer(() => {
|
||||
// store
|
||||
const {
|
||||
user: { fetchCurrentUser },
|
||||
} = useMobxStore();
|
||||
// store hooks
|
||||
const { fetchCurrentUser } = useUser();
|
||||
|
||||
const mutateUserInfo = useCallback(() => {
|
||||
fetchCurrentUser();
|
||||
|
@ -8,8 +8,8 @@ import { mutate } from "swr";
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
// icons
|
||||
import { LogIn, LogOut, Settings, UserCog2 } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication, useUser } from "hooks/store";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// ui
|
||||
@ -26,13 +26,14 @@ const PROFILE_LINKS = [
|
||||
];
|
||||
|
||||
export const InstanceSidebarDropdown = observer(() => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
// store
|
||||
// store hooks
|
||||
const {
|
||||
theme: { sidebarCollapsed },
|
||||
workspace: { workspaceSlug },
|
||||
user: { signOut, currentUser, currentUserSettings },
|
||||
} = useMobxStore();
|
||||
router: { workspaceSlug },
|
||||
} = useApplication();
|
||||
const { signOut, currentUser, currentUserSettings } = useUser();
|
||||
// hooks
|
||||
const { setToastAlert } = useToast();
|
||||
const { setTheme } = useTheme();
|
||||
|
@ -1,9 +1,8 @@
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
// icons
|
||||
import { Image, BrainCog, Cog, Lock, Mail } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
// ui
|
||||
import { Tooltip } from "@plane/ui";
|
||||
|
||||
@ -41,9 +40,10 @@ const INSTANCE_ADMIN_LINKS = [
|
||||
];
|
||||
|
||||
export const InstanceAdminSidebarMenu = () => {
|
||||
// store hooks
|
||||
const {
|
||||
theme: { sidebarCollapsed },
|
||||
} = useMobxStore();
|
||||
} = useApplication();
|
||||
// router
|
||||
const router = useRouter();
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
import useIntegrationPopup from "hooks/use-integration-popup";
|
||||
// ui
|
||||
import { Button } from "@plane/ui";
|
||||
// types
|
||||
import { IWorkspaceIntegration } from "types";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
type Props = {
|
||||
workspaceIntegration: false | IWorkspaceIntegration | undefined;
|
||||
@ -13,9 +13,10 @@ type Props = {
|
||||
};
|
||||
|
||||
export const GithubAuth: React.FC<Props> = observer(({ workspaceIntegration, provider }) => {
|
||||
// store hooks
|
||||
const {
|
||||
appConfig: { envConfig },
|
||||
} = useMobxStore();
|
||||
config: { envConfig },
|
||||
} = useApplication();
|
||||
// hooks
|
||||
const { startAuth, isConnecting } = useIntegrationPopup({
|
||||
provider,
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { FC } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Control, Controller, UseFormWatch } from "react-hook-form";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useProject } from "hooks/store";
|
||||
// components
|
||||
import { SelectRepository, TFormValues, TIntegrationSteps } from "components/integration";
|
||||
// ui
|
||||
@ -22,21 +21,18 @@ type Props = {
|
||||
|
||||
export const GithubImportData: FC<Props> = observer((props) => {
|
||||
const { handleStepChange, integration, control, watch } = props;
|
||||
// store hooks
|
||||
const { workspaceProjects, getProjectById } = useProject();
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
const options = workspaceProjects?.map((projectId) => {
|
||||
const projectDetails = getProjectById(projectId);
|
||||
|
||||
const { project: projectStore } = useMobxStore();
|
||||
|
||||
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined;
|
||||
|
||||
const options = projects
|
||||
? projects.map((project) => ({
|
||||
value: project.id,
|
||||
query: project.name,
|
||||
content: <p>{truncateText(project.name, 25)}</p>,
|
||||
}))
|
||||
: undefined;
|
||||
return {
|
||||
value: `${projectDetails?.id}`,
|
||||
query: `${projectDetails?.name}`,
|
||||
content: <p>{truncateText(projectDetails?.name ?? "", 25)}</p>,
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="mt-6">
|
||||
@ -74,7 +70,7 @@ export const GithubImportData: FC<Props> = observer((props) => {
|
||||
<p className="text-xs text-custom-text-200">Select the project to import the issues to.</p>
|
||||
</div>
|
||||
<div className="col-span-12 sm:col-span-4">
|
||||
{projects && (
|
||||
{workspaceProjects && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="project"
|
||||
@ -82,11 +78,7 @@ export const GithubImportData: FC<Props> = observer((props) => {
|
||||
<CustomSearchSelect
|
||||
value={value}
|
||||
label={
|
||||
value ? (
|
||||
projects.find((p) => p.id === value)?.name
|
||||
) : (
|
||||
<span className="text-custom-text-200">Select Project</span>
|
||||
)
|
||||
value ? getProjectById(value)?.name : <span className="text-custom-text-200">Select Project</span>
|
||||
}
|
||||
onChange={onChange}
|
||||
options={options}
|
||||
|
@ -1,28 +1,23 @@
|
||||
import React from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import Link from "next/link";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useFormContext, Controller } from "react-hook-form";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// icons
|
||||
import { Plus } from "lucide-react";
|
||||
// hooks
|
||||
import { useApplication, useProject } from "hooks/store";
|
||||
// components
|
||||
import { CustomSelect, Input } from "@plane/ui";
|
||||
// types
|
||||
import { IJiraImporterForm } from "types";
|
||||
|
||||
export const JiraGetImportDetail: React.FC = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
// store hooks
|
||||
const {
|
||||
project: projectStore,
|
||||
commandPalette: commandPaletteStore,
|
||||
trackEvent: { setTrackElement },
|
||||
} = useMobxStore();
|
||||
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined;
|
||||
|
||||
eventTracker: { setTrackElement },
|
||||
} = useApplication();
|
||||
const { workspaceProjects, getProjectById } = useProject();
|
||||
// form info
|
||||
const {
|
||||
control,
|
||||
formState: { errors },
|
||||
@ -170,20 +165,26 @@ export const JiraGetImportDetail: React.FC = observer(() => {
|
||||
onChange={onChange}
|
||||
label={
|
||||
<span>
|
||||
{value && value !== "" ? (
|
||||
projects?.find((p) => p.id === value)?.name
|
||||
{value && value.trim() !== "" ? (
|
||||
getProjectById(value)?.name
|
||||
) : (
|
||||
<span className="text-custom-text-200">Select a project</span>
|
||||
)}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
{projects && projects.length > 0 ? (
|
||||
projects.map((project) => (
|
||||
<CustomSelect.Option key={project.id} value={project.id}>
|
||||
{project.name}
|
||||
</CustomSelect.Option>
|
||||
))
|
||||
{workspaceProjects && workspaceProjects.length > 0 ? (
|
||||
workspaceProjects.map((projectId) => {
|
||||
const projectDetails = getProjectById(projectId);
|
||||
|
||||
if (!projectDetails) return;
|
||||
|
||||
return (
|
||||
<CustomSelect.Option key={projectId} value={projectId}>
|
||||
{projectDetails.name}
|
||||
</CustomSelect.Option>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<div className="flex cursor-pointer select-none items-center space-x-2 truncate rounded px-1 py-1.5 text-custom-text-200">
|
||||
<p>You don{"'"}t have any project. Please create a project first.</p>
|
||||
|
@ -2,12 +2,12 @@ import { useState } from "react";
|
||||
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import { observer } from "mobx-react-lite";
|
||||
import useSWR, { mutate } from "swr";
|
||||
|
||||
// services
|
||||
import { IntegrationService } from "services/integrations";
|
||||
// hooks
|
||||
import { useApplication, useUser } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
import useIntegrationPopup from "hooks/use-integration-popup";
|
||||
// ui
|
||||
@ -20,8 +20,6 @@ import { CheckCircle } from "lucide-react";
|
||||
import { IAppIntegration, IWorkspaceIntegration } from "types";
|
||||
// fetch-keys
|
||||
import { WORKSPACE_INTEGRATIONS } from "constants/fetch-keys";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
type Props = {
|
||||
integration: IAppIntegration;
|
||||
@ -44,20 +42,23 @@ const integrationDetails: { [key: string]: any } = {
|
||||
const integrationService = new IntegrationService();
|
||||
|
||||
export const SingleIntegrationCard: React.FC<Props> = observer(({ integration }) => {
|
||||
const {
|
||||
appConfig: { envConfig },
|
||||
user: { currentWorkspaceRole },
|
||||
} = useMobxStore();
|
||||
|
||||
const isUserAdmin = currentWorkspaceRole === 20;
|
||||
|
||||
// states
|
||||
const [deletingIntegration, setDeletingIntegration] = useState(false);
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
// store hooks
|
||||
const {
|
||||
config: { envConfig },
|
||||
} = useApplication();
|
||||
const {
|
||||
membership: { currentWorkspaceRole },
|
||||
} = useUser();
|
||||
// toast alert
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const isUserAdmin = currentWorkspaceRole === 20;
|
||||
|
||||
const { startAuth, isConnecting: isInstalling } = useIntegrationPopup({
|
||||
provider: integration.provider,
|
||||
github_app_name: envConfig?.github_app_name || "",
|
||||
|
@ -2,18 +2,17 @@ import { useState, useEffect } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import useSWR, { mutate } from "swr";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
import useIntegrationPopup from "hooks/use-integration-popup";
|
||||
// services
|
||||
import { AppInstallationService } from "services/app_installation.service";
|
||||
// ui
|
||||
import { Loader } from "@plane/ui";
|
||||
// hooks
|
||||
import useIntegrationPopup from "hooks/use-integration-popup";
|
||||
// types
|
||||
import { IWorkspaceIntegration, ISlackIntegration } from "types";
|
||||
// fetch-keys
|
||||
import { SLACK_CHANNEL_INFO } from "constants/fetch-keys";
|
||||
// lib
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
type Props = {
|
||||
integration: IWorkspaceIntegration;
|
||||
@ -22,10 +21,10 @@ type Props = {
|
||||
const appInstallationService = new AppInstallationService();
|
||||
|
||||
export const SelectChannel: React.FC<Props> = observer(({ integration }) => {
|
||||
// store
|
||||
// store hooks
|
||||
const {
|
||||
appConfig: { envConfig },
|
||||
} = useMobxStore();
|
||||
config: { envConfig },
|
||||
} = useApplication();
|
||||
// states
|
||||
const [slackChannelAvailabilityToggle, setSlackChannelAvailabilityToggle] = useState<boolean>(false);
|
||||
const [slackChannel, setSlackChannel] = useState<ISlackIntegration | null>(null);
|
||||
|
@ -3,12 +3,11 @@ import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { mutate } from "swr";
|
||||
import { useDropzone } from "react-dropzone";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
// services
|
||||
import { IssueAttachmentService } from "services/issue";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// types
|
||||
import { IIssueAttachment } from "types";
|
||||
// fetch-keys
|
||||
@ -29,12 +28,12 @@ export const IssueAttachmentUpload: React.FC<Props> = observer((props) => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, issueId } = router.query;
|
||||
|
||||
// toast alert
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
// store hooks
|
||||
const {
|
||||
appConfig: { envConfig },
|
||||
} = useMobxStore();
|
||||
config: { envConfig },
|
||||
} = useApplication();
|
||||
|
||||
const onDrop = useCallback((acceptedFiles: File[]) => {
|
||||
if (!acceptedFiles[0] || !workspaceSlug) return;
|
||||
|
@ -1,12 +1,13 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useUser } from "hooks/store";
|
||||
import useEditorSuggestions from "hooks/use-editor-suggestions";
|
||||
// services
|
||||
import { FileService } from "services/file.service";
|
||||
// icons
|
||||
import { Check, Globe2, Lock, MessageSquare, Pencil, Trash2, X } from "lucide-react";
|
||||
// hooks
|
||||
import useUser from "hooks/use-user";
|
||||
// ui
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
import { CommentReaction } from "components/issues";
|
||||
@ -15,7 +16,6 @@ import { LiteTextEditorWithRef, LiteReadOnlyEditorWithRef } from "@plane/lite-te
|
||||
import { timeAgo } from "helpers/date-time.helper";
|
||||
// types
|
||||
import type { IIssueActivity } from "types";
|
||||
import useEditorSuggestions from "hooks/use-editor-suggestions";
|
||||
|
||||
// services
|
||||
const fileService = new FileService();
|
||||
@ -28,22 +28,18 @@ type Props = {
|
||||
workspaceSlug: string;
|
||||
};
|
||||
|
||||
export const CommentCard: React.FC<Props> = ({
|
||||
comment,
|
||||
handleCommentDeletion,
|
||||
onSubmit,
|
||||
showAccessSpecifier = false,
|
||||
workspaceSlug,
|
||||
}) => {
|
||||
const { user } = useUser();
|
||||
|
||||
export const CommentCard: React.FC<Props> = observer((props) => {
|
||||
const { comment, handleCommentDeletion, onSubmit, showAccessSpecifier = false, workspaceSlug } = props;
|
||||
// states
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
// refs
|
||||
const editorRef = React.useRef<any>(null);
|
||||
const showEditorRef = React.useRef<any>(null);
|
||||
|
||||
const editorSuggestions = useEditorSuggestions();
|
||||
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
|
||||
// store hooks
|
||||
const { currentUser } = useUser();
|
||||
// form info
|
||||
const {
|
||||
formState: { isSubmitting },
|
||||
handleSubmit,
|
||||
@ -152,7 +148,7 @@ export const CommentCard: React.FC<Props> = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{user?.id === comment.actor && (
|
||||
{currentUser?.id === comment.actor && (
|
||||
<CustomMenu ellipsis>
|
||||
<CustomMenu.MenuItem onClick={() => setIsEditing(true)} className="flex items-center gap-1">
|
||||
<Pencil className="h-3 w-3" />
|
||||
@ -192,4 +188,4 @@ export const CommentCard: React.FC<Props> = ({
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -1,12 +1,14 @@
|
||||
import { FC } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import useUser from "hooks/use-user";
|
||||
import { useUser } from "hooks/store";
|
||||
import useCommentReaction from "hooks/use-comment-reaction";
|
||||
// ui
|
||||
import { ReactionSelector } from "components/core";
|
||||
// helper
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
// types
|
||||
import { IssueCommentReaction } from "types";
|
||||
|
||||
type Props = {
|
||||
@ -15,13 +17,13 @@ type Props = {
|
||||
readonly?: boolean;
|
||||
};
|
||||
|
||||
export const CommentReaction: FC<Props> = (props) => {
|
||||
export const CommentReaction: FC<Props> = observer((props) => {
|
||||
const { projectId, commentId, readonly = false } = props;
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const { user } = useUser();
|
||||
// store hooks
|
||||
const { currentUser } = useUser();
|
||||
|
||||
const { commentReactions, groupedReactions, handleReactionCreate, handleReactionDelete } = useCommentReaction(
|
||||
workspaceSlug,
|
||||
@ -33,7 +35,7 @@ export const CommentReaction: FC<Props> = (props) => {
|
||||
if (!workspaceSlug || !projectId || !commentId) return;
|
||||
|
||||
const isSelected = commentReactions?.some(
|
||||
(r: IssueCommentReaction) => r.actor === user?.id && r.reaction === reaction
|
||||
(r: IssueCommentReaction) => r.actor === currentUser?.id && r.reaction === reaction
|
||||
);
|
||||
|
||||
if (isSelected) {
|
||||
@ -51,7 +53,7 @@ export const CommentReaction: FC<Props> = (props) => {
|
||||
position="top"
|
||||
value={
|
||||
commentReactions
|
||||
?.filter((reaction: IssueCommentReaction) => reaction.actor === user?.id)
|
||||
?.filter((reaction: IssueCommentReaction) => reaction.actor === currentUser?.id)
|
||||
.map((r: IssueCommentReaction) => r.reaction) || []
|
||||
}
|
||||
onSelect={handleReactionClick}
|
||||
@ -70,7 +72,9 @@ export const CommentReaction: FC<Props> = (props) => {
|
||||
}}
|
||||
key={reaction}
|
||||
className={`flex h-full items-center gap-1 rounded-md px-2 py-1 text-sm text-custom-text-100 ${
|
||||
commentReactions?.some((r: IssueCommentReaction) => r.actor === user?.id && r.reaction === reaction)
|
||||
commentReactions?.some(
|
||||
(r: IssueCommentReaction) => r.actor === currentUser?.id && r.reaction === reaction
|
||||
)
|
||||
? "bg-custom-primary-100/10"
|
||||
: "bg-custom-background-80"
|
||||
}`}
|
||||
@ -78,7 +82,9 @@ export const CommentReaction: FC<Props> = (props) => {
|
||||
<span>{renderEmoji(reaction)}</span>
|
||||
<span
|
||||
className={
|
||||
commentReactions?.some((r: IssueCommentReaction) => r.actor === user?.id && r.reaction === reaction)
|
||||
commentReactions?.some(
|
||||
(r: IssueCommentReaction) => r.actor === currentUser?.id && r.reaction === reaction
|
||||
)
|
||||
? "text-custom-primary-100"
|
||||
: ""
|
||||
}
|
||||
@ -90,4 +96,4 @@ export const CommentReaction: FC<Props> = (props) => {
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -1,9 +1,6 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// services
|
||||
import { IssueDraftService } from "services/issue";
|
||||
// hooks
|
||||
@ -24,17 +21,14 @@ type Props = {
|
||||
|
||||
const issueDraftService = new IssueDraftService();
|
||||
|
||||
export const DeleteDraftIssueModal: React.FC<Props> = observer((props) => {
|
||||
export const DeleteDraftIssueModal: React.FC<Props> = (props) => {
|
||||
const { isOpen, handleClose, data, onSubmit } = props;
|
||||
|
||||
// states
|
||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||
|
||||
const { user: userStore } = useMobxStore();
|
||||
const user = userStore.currentUser;
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
// toast alert
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
useEffect(() => {
|
||||
@ -47,12 +41,12 @@ export const DeleteDraftIssueModal: React.FC<Props> = observer((props) => {
|
||||
};
|
||||
|
||||
const handleDeletion = async () => {
|
||||
if (!workspaceSlug || !data || !user) return;
|
||||
if (!workspaceSlug || !data) return;
|
||||
|
||||
setIsDeleteLoading(true);
|
||||
|
||||
await issueDraftService
|
||||
.deleteDraftIssue(workspaceSlug as string, data.project, data.id)
|
||||
.deleteDraftIssue(workspaceSlug.toString(), data.project, data.id)
|
||||
.then(() => {
|
||||
setIsDeleteLoading(false);
|
||||
handleClose();
|
||||
@ -138,4 +132,4 @@ export const DeleteDraftIssueModal: React.FC<Props> = observer((props) => {
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
@ -135,7 +135,7 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = (props) => {
|
||||
debouncedFormSave();
|
||||
}}
|
||||
required
|
||||
className="min-h-10 block w-full resize-none overflow-hidden rounded border-none bg-transparent px-3 py-2 text-xl outline-none ring-0 focus:ring-1 focus:ring-custom-primary"
|
||||
className="min-h-min block w-full resize-none overflow-hidden rounded border-none bg-transparent px-3 py-2 text-2xl font-medium outline-none ring-0 focus:ring-1 focus:ring-custom-primary"
|
||||
hasError={Boolean(errors?.description)}
|
||||
role="textbox"
|
||||
disabled={!isAllowed}
|
||||
|
@ -1,12 +1,16 @@
|
||||
import React, { FC, useState, useEffect, useRef } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Sparkle, X } from "lucide-react";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
import useLocalStorage from "hooks/use-local-storage";
|
||||
import useEditorSuggestions from "hooks/use-editor-suggestions";
|
||||
// services
|
||||
import { AIService } from "services/ai.service";
|
||||
import { FileService } from "services/file.service";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
import useLocalStorage from "hooks/use-local-storage";
|
||||
// components
|
||||
import { GptAssistantModal } from "components/core";
|
||||
import { ParentIssuesListModal } from "components/issues";
|
||||
@ -21,18 +25,11 @@ import {
|
||||
} from "components/issues/select";
|
||||
import { CreateStateModal } from "components/states";
|
||||
import { CreateLabelModal } from "components/labels";
|
||||
import { RichTextEditorWithRef } from "@plane/rich-text-editor";
|
||||
// ui
|
||||
import {} from "components/ui";
|
||||
import { Button, CustomMenu, Input, ToggleSwitch } from "@plane/ui";
|
||||
// icons
|
||||
import { Sparkle, X } from "lucide-react";
|
||||
// types
|
||||
import type { IUser, IIssue, ISearchIssueResponse } from "types";
|
||||
// components
|
||||
import { RichTextEditorWithRef } from "@plane/rich-text-editor";
|
||||
import useEditorSuggestions from "hooks/use-editor-suggestions";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
const aiService = new AIService();
|
||||
const fileService = new FileService();
|
||||
@ -123,8 +120,8 @@ export const DraftIssueForm: FC<IssueFormProps> = observer((props) => {
|
||||
const { workspaceSlug } = router.query;
|
||||
// store
|
||||
const {
|
||||
appConfig: { envConfig },
|
||||
} = useMobxStore();
|
||||
config: { envConfig },
|
||||
} = useApplication();
|
||||
// form info
|
||||
const {
|
||||
formState: { errors, isSubmitting },
|
||||
|
@ -2,8 +2,10 @@ import React, { FC, useState, useEffect, useRef } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { LayoutPanelTop, Sparkle, X } from "lucide-react";
|
||||
// hooks
|
||||
import { useApplication, useUser } from "hooks/store";
|
||||
import useEditorSuggestions from "hooks/use-editor-suggestions";
|
||||
// services
|
||||
import { AIService } from "services/ai.service";
|
||||
import { FileService } from "services/file.service";
|
||||
@ -25,15 +27,11 @@ import {
|
||||
} from "components/issues/select";
|
||||
import { CreateStateModal } from "components/states";
|
||||
import { CreateLabelModal } from "components/labels";
|
||||
import { RichTextEditorWithRef } from "@plane/rich-text-editor";
|
||||
// ui
|
||||
import { Button, CustomMenu, Input, ToggleSwitch } from "@plane/ui";
|
||||
// icons
|
||||
import { LayoutPanelTop, Sparkle, X } from "lucide-react";
|
||||
// types
|
||||
import type { IIssue, ISearchIssueResponse } from "types";
|
||||
// components
|
||||
import { RichTextEditorWithRef } from "@plane/rich-text-editor";
|
||||
import useEditorSuggestions from "hooks/use-editor-suggestions";
|
||||
|
||||
const defaultValues: Partial<IIssue> = {
|
||||
project: "",
|
||||
@ -106,12 +104,11 @@ export const IssueForm: FC<IssueFormProps> = observer((props) => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
// store
|
||||
// store hooks
|
||||
const {
|
||||
user: userStore,
|
||||
appConfig: { envConfig },
|
||||
} = useMobxStore();
|
||||
const user = userStore.currentUser;
|
||||
config: { envConfig },
|
||||
} = useApplication();
|
||||
const {} = useUser();
|
||||
// hooks
|
||||
const editorSuggestion = useEditorSuggestions();
|
||||
const { setToastAlert } = useToast();
|
||||
@ -183,12 +180,12 @@ export const IssueForm: FC<IssueFormProps> = observer((props) => {
|
||||
};
|
||||
|
||||
const handleAutoGenerateDescription = async () => {
|
||||
if (!workspaceSlug || !projectId || !user) return;
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
setIAmFeelingLucky(true);
|
||||
|
||||
aiService
|
||||
.createGptTask(workspaceSlug as string, projectId as string, {
|
||||
.createGptTask(workspaceSlug.toString(), projectId.toString(), {
|
||||
prompt: issueName,
|
||||
task: "Generate a proper description for this issue.",
|
||||
})
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Droppable } from "@hello-pangea/dnd";
|
||||
// components
|
||||
@ -48,11 +49,12 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
|
||||
quickAddCallback,
|
||||
viewId,
|
||||
} = props;
|
||||
|
||||
const [showAllIssues, setShowAllIssues] = useState(false);
|
||||
const calendarLayout = issuesFilterStore?.issueFilters?.displayFilters?.calendar?.layout ?? "month";
|
||||
|
||||
const issueIdList = groupedIssueIds ? groupedIssueIds[renderDateFormat(date.date)] : null;
|
||||
|
||||
const totalIssues = issueIdList?.length ?? 0;
|
||||
return (
|
||||
<>
|
||||
<div className="group relative flex h-full w-full flex-col bg-custom-background-90">
|
||||
@ -87,7 +89,13 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
|
||||
{...provided.droppableProps}
|
||||
ref={provided.innerRef}
|
||||
>
|
||||
<CalendarIssueBlocks issues={issues} issueIdList={issueIdList} quickActions={quickActions} />
|
||||
<CalendarIssueBlocks
|
||||
issues={issues}
|
||||
issueIdList={issueIdList}
|
||||
quickActions={quickActions}
|
||||
showAllIssues={showAllIssues}
|
||||
/>
|
||||
|
||||
{enableQuickIssueCreate && !disableIssueCreation && (
|
||||
<div className="px-2 py-1">
|
||||
<CalendarQuickAddIssueForm
|
||||
@ -98,9 +106,23 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
|
||||
}}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
onOpen={() => setShowAllIssues(true)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{totalIssues > 4 && (
|
||||
<div className="flex items-center px-2.5 py-1">
|
||||
<button
|
||||
type="button"
|
||||
className="w-min whitespace-nowrap rounded text-xs px-1.5 py-1 text-custom-text-400 font-medium hover:bg-custom-background-80 hover:text-custom-text-300"
|
||||
onClick={() => setShowAllIssues((prevData) => !prevData)}
|
||||
>
|
||||
{showAllIssues ? "Hide" : totalIssues - 4 + " more"}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
|
@ -15,10 +15,11 @@ type Props = {
|
||||
issues: IIssueResponse | undefined;
|
||||
issueIdList: string[] | null;
|
||||
quickActions: (issue: IIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||
showAllIssues?: boolean;
|
||||
};
|
||||
|
||||
export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
|
||||
const { issues, issueIdList, quickActions } = props;
|
||||
const { issues, issueIdList, quickActions, showAllIssues = false } = props;
|
||||
// router
|
||||
const router = useRouter();
|
||||
|
||||
@ -52,7 +53,7 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{issueIdList?.map((issueId, index) => {
|
||||
{issueIdList?.slice(0, showAllIssues ? issueIdList.length : 4).map((issueId, index) => {
|
||||
if (!issues?.[issueId]) return null;
|
||||
|
||||
const issue = issues?.[issueId];
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user