forked from github/plane
Compare commits
57 Commits
chore/proj
...
dev-docker
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd9cebc7af | ||
|
|
b77ddf9c95 | ||
|
|
4133798a81 | ||
|
|
761aefa73b | ||
|
|
561fb9815b | ||
|
|
2cc67f6498 | ||
|
|
fa403ef4ae | ||
|
|
eee6658cc2 | ||
|
|
68b438ab1a | ||
|
|
3ce0aa8ebc | ||
|
|
b406a70e72 | ||
|
|
b02417120b | ||
|
|
d040394826 | ||
|
|
f7682c57ba | ||
|
|
9bb6254515 | ||
|
|
ae052f1890 | ||
|
|
cfc7049343 | ||
|
|
41e55dff85 | ||
|
|
0bccb63a9f | ||
|
|
2eb956e97e | ||
|
|
d470adf262 | ||
|
|
cebc8bdc8d | ||
|
|
64b5ba196f | ||
|
|
8d5018318d | ||
|
|
0fbdc0b157 | ||
|
|
2f39181eb7 | ||
|
|
d825dc5579 | ||
|
|
1f8117c987 | ||
|
|
125e9090ea | ||
|
|
02ac4cee22 | ||
|
|
93164755e2 | ||
|
|
6344f6f562 | ||
|
|
b67e30fd9c | ||
|
|
93fec2c678 | ||
|
|
c3c6ba9e34 | ||
|
|
d74ec7bda9 | ||
|
|
e593a8d4bd | ||
|
|
abb8782c44 | ||
|
|
0afd72db95 | ||
|
|
65295f6c6f | ||
|
|
5b6b43fb83 | ||
|
|
f8497125db | ||
|
|
b24622e5ef | ||
|
|
10dface85d | ||
|
|
2b6debaa3e | ||
|
|
1750ba344b | ||
|
|
550473bb02 | ||
|
|
fde978861c | ||
|
|
f44d142f2c | ||
|
|
1ded8f486f | ||
|
|
2c43a15515 | ||
|
|
0979acc1a4 | ||
|
|
9003c58d89 | ||
|
|
9b4aebc385 | ||
|
|
9828d2332a | ||
|
|
9f69fe6060 | ||
|
|
d9339b8f8e |
@@ -4,7 +4,7 @@ module.exports = {
|
||||
extends: ["custom"],
|
||||
settings: {
|
||||
next: {
|
||||
rootDir: ["apps/*"],
|
||||
rootDir: ["web/", "deploy/"],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
13
Dockerfile
13
Dockerfile
@@ -5,9 +5,11 @@ WORKDIR /app
|
||||
ENV NEXT_PUBLIC_API_BASE_URL=http://NEXT_PUBLIC_API_BASE_URL_PLACEHOLDER
|
||||
|
||||
RUN yarn global add turbo
|
||||
RUN apk add tree
|
||||
COPY . .
|
||||
|
||||
RUN turbo prune --scope=app --docker
|
||||
RUN turbo prune --scope=app --scope=plane-deploy --docker
|
||||
CMD tree -I node_modules/
|
||||
|
||||
# Add lockfile and package.json's of isolated subworkspace
|
||||
FROM node:18-alpine AS installer
|
||||
@@ -21,14 +23,14 @@ COPY --from=builder /app/out/json/ .
|
||||
COPY --from=builder /app/out/yarn.lock ./yarn.lock
|
||||
RUN yarn install
|
||||
|
||||
# Build the project
|
||||
# # Build the project
|
||||
COPY --from=builder /app/out/full/ .
|
||||
COPY turbo.json turbo.json
|
||||
COPY replace-env-vars.sh /usr/local/bin/
|
||||
USER root
|
||||
RUN chmod +x /usr/local/bin/replace-env-vars.sh
|
||||
|
||||
RUN yarn turbo run build --filter=app
|
||||
RUN yarn turbo run build
|
||||
|
||||
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL \
|
||||
BUILT_NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
|
||||
@@ -96,11 +98,16 @@ RUN adduser --system --uid 1001 captain
|
||||
|
||||
COPY --from=installer /app/apps/app/next.config.js .
|
||||
COPY --from=installer /app/apps/app/package.json .
|
||||
COPY --from=installer /app/apps/space/next.config.js .
|
||||
COPY --from=installer /app/apps/space/package.json .
|
||||
|
||||
COPY --from=installer --chown=captain:plane /app/apps/app/.next/standalone ./
|
||||
|
||||
COPY --from=installer --chown=captain:plane /app/apps/app/.next/static ./apps/app/.next/static
|
||||
|
||||
COPY --from=installer --chown=captain:plane /app/apps/space/.next/standalone ./
|
||||
COPY --from=installer --chown=captain:plane /app/apps/space/.next ./apps/space/.next
|
||||
|
||||
ENV NEXT_TELEMETRY_DISABLED 1
|
||||
|
||||
# RUN rm /etc/nginx/conf.d/default.conf
|
||||
|
||||
10
README.md
10
README.md
@@ -61,6 +61,16 @@ chmod +x setup.sh
|
||||
|
||||
> If running in a cloud env replace localhost with public facing IP address of the VM
|
||||
|
||||
- Setup Tiptap Pro
|
||||
|
||||
Visit [Tiptap Pro](https://collab.tiptap.dev/pro-extensions) and signup (it is free).
|
||||
|
||||
Create a **`.npmrc`** file, copy the following and replace your registry token generated from Tiptap Pro.
|
||||
|
||||
```
|
||||
@tiptap-pro:registry=https://registry.tiptap.dev/
|
||||
//registry.tiptap.dev/:_authToken=YOUR_REGISTRY_TOKEN
|
||||
```
|
||||
- Run Docker compose up
|
||||
|
||||
```bash
|
||||
|
||||
@@ -14,6 +14,11 @@ from plane.db.models import Cycle, CycleIssue, CycleFavorite
|
||||
|
||||
class CycleWriteSerializer(BaseSerializer):
|
||||
|
||||
def validate(self, data):
|
||||
if data.get("start_date", None) is not None and data.get("end_date", None) is not None and data.get("start_date", None) > data.get("end_date", None):
|
||||
raise serializers.ValidationError("Start date cannot exceed end date")
|
||||
return data
|
||||
|
||||
class Meta:
|
||||
model = Cycle
|
||||
fields = "__all__"
|
||||
@@ -35,6 +40,11 @@ class CycleSerializer(BaseSerializer):
|
||||
started_estimates = serializers.IntegerField(read_only=True)
|
||||
workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace")
|
||||
project_detail = ProjectLiteSerializer(read_only=True, source="project")
|
||||
|
||||
def validate(self, data):
|
||||
if data.get("start_date", None) is not None and data.get("end_date", None) is not None and data.get("start_date", None) > data.get("end_date", None):
|
||||
raise serializers.ValidationError("Start date cannot exceed end date")
|
||||
return data
|
||||
|
||||
def get_assignees(self, obj):
|
||||
members = [
|
||||
|
||||
@@ -40,6 +40,11 @@ class ModuleWriteSerializer(BaseSerializer):
|
||||
"updated_at",
|
||||
]
|
||||
|
||||
def validate(self, data):
|
||||
if data.get("start_date", None) is not None and data.get("target_date", None) is not None and data.get("start_date", None) > data.get("target_date", None):
|
||||
raise serializers.ValidationError("Start date cannot exceed target date")
|
||||
return data
|
||||
|
||||
def create(self, validated_data):
|
||||
members = validated_data.pop("members_list", None)
|
||||
|
||||
|
||||
@@ -88,6 +88,7 @@ class ProjectLiteSerializer(BaseSerializer):
|
||||
"cover_image",
|
||||
"icon_prop",
|
||||
"emoji",
|
||||
"description",
|
||||
]
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
@@ -370,7 +370,7 @@ class UserWorkSpaceIssues(BaseAPIView):
|
||||
)
|
||||
)
|
||||
.filter(**filters)
|
||||
)
|
||||
).distinct()
|
||||
|
||||
# Priority Ordering
|
||||
if order_by_param == "priority" or order_by_param == "-priority":
|
||||
|
||||
@@ -994,11 +994,11 @@ class UserWorkspaceDashboardEndpoint(BaseAPIView):
|
||||
|
||||
upcoming_issues = Issue.issue_objects.filter(
|
||||
~Q(state__group__in=["completed", "cancelled"]),
|
||||
target_date__gte=timezone.now(),
|
||||
start_date__gte=timezone.now(),
|
||||
workspace__slug=slug,
|
||||
assignees__in=[request.user],
|
||||
completed_at__isnull=True,
|
||||
).values("id", "name", "workspace__slug", "project_id", "target_date")
|
||||
).values("id", "name", "workspace__slug", "project_id", "start_date")
|
||||
|
||||
return Response(
|
||||
{
|
||||
@@ -1083,6 +1083,7 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView):
|
||||
.filter(**filters)
|
||||
.values("priority")
|
||||
.annotate(priority_count=Count("priority"))
|
||||
.filter(priority_count__gte=1)
|
||||
.annotate(
|
||||
priority_order=Case(
|
||||
*[
|
||||
|
||||
@@ -68,7 +68,7 @@ def create_zip_file(files):
|
||||
def upload_to_s3(zip_file, workspace_id, token_id, slug):
|
||||
s3 = boto3.client(
|
||||
"s3",
|
||||
region_name="ap-south-1",
|
||||
region_name=settings.AWS_REGION,
|
||||
aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
|
||||
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
|
||||
config=Config(signature_version="s3v4"),
|
||||
|
||||
@@ -184,19 +184,24 @@ def track_description(
|
||||
if current_instance.get("description_html") != requested_data.get(
|
||||
"description_html"
|
||||
):
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
issue_id=issue_id,
|
||||
actor=actor,
|
||||
verb="updated",
|
||||
old_value=current_instance.get("description_html"),
|
||||
new_value=requested_data.get("description_html"),
|
||||
field="description",
|
||||
project=project,
|
||||
workspace=project.workspace,
|
||||
comment=f"updated the description to {requested_data.get('description_html')}",
|
||||
)
|
||||
)
|
||||
last_activity = IssueActivity.objects.filter(issue_id=issue_id).order_by("-created_at").first()
|
||||
if(last_activity is not None and last_activity.field == "description" and actor.id == last_activity.actor_id):
|
||||
last_activity.created_at = timezone.now()
|
||||
last_activity.save(update_fields=["created_at"])
|
||||
else:
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
issue_id=issue_id,
|
||||
actor=actor,
|
||||
verb="updated",
|
||||
old_value=current_instance.get("description_html"),
|
||||
new_value=requested_data.get("description_html"),
|
||||
field="description",
|
||||
project=project,
|
||||
workspace=project.workspace,
|
||||
comment=f"updated the description to {requested_data.get('description_html')}",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
# Track changes in issue target date
|
||||
|
||||
@@ -227,6 +227,11 @@ class Migration(migrations.Migration):
|
||||
'unique_together': {('issue', 'actor')},
|
||||
},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='modulelink',
|
||||
name='title',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
migrations.RunPython(generate_display_name),
|
||||
migrations.RunPython(rectify_field_issue_activity),
|
||||
migrations.RunPython(update_assignee_issue_activity),
|
||||
|
||||
@@ -98,7 +98,7 @@ class ModuleIssue(ProjectBaseModel):
|
||||
|
||||
|
||||
class ModuleLink(ProjectBaseModel):
|
||||
title = models.CharField(max_length=255, null=True)
|
||||
title = models.CharField(max_length=255, blank=True, null=True)
|
||||
url = models.URLField()
|
||||
module = models.ForeignKey(
|
||||
Module, on_delete=models.CASCADE, related_name="link_module"
|
||||
|
||||
85
app.json
85
app.json
@@ -1,85 +0,0 @@
|
||||
{
|
||||
"name": "Plane",
|
||||
"description": "Plane helps you track your issues, epics, and product roadmaps.",
|
||||
"repository": "http://github.com/makeplane/plane",
|
||||
"logo": "https://avatars.githubusercontent.com/u/115727700?s=200&v=4",
|
||||
"website": "https://plane.so/",
|
||||
"success_url": "/",
|
||||
"stack": "heroku-22",
|
||||
"keywords": [
|
||||
"plane",
|
||||
"project management",
|
||||
"django",
|
||||
"next"
|
||||
],
|
||||
"addons": [
|
||||
"heroku-postgresql:mini",
|
||||
"heroku-redis:mini"
|
||||
],
|
||||
"buildpacks": [
|
||||
{
|
||||
"url": "https://github.com/heroku/heroku-buildpack-python.git"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/heroku/heroku-buildpack-nodejs#v176"
|
||||
}
|
||||
],
|
||||
"env": {
|
||||
"EMAIL_HOST": {
|
||||
"description": "Email host to send emails from",
|
||||
"value": ""
|
||||
},
|
||||
"EMAIL_HOST_USER": {
|
||||
"description": "Email host to send emails from",
|
||||
"value": ""
|
||||
},
|
||||
"EMAIL_HOST_PASSWORD": {
|
||||
"description": "Email host to send emails from",
|
||||
"value": ""
|
||||
},
|
||||
"EMAIL_FROM": {
|
||||
"description": "Email Sender",
|
||||
"value": ""
|
||||
},
|
||||
"EMAIL_PORT": {
|
||||
"description": "The default Email PORT to use",
|
||||
"value": "587"
|
||||
},
|
||||
"AWS_REGION": {
|
||||
"description": "AWS Region to use for S3",
|
||||
"value": "false"
|
||||
},
|
||||
"AWS_ACCESS_KEY_ID": {
|
||||
"description": "AWS Access Key ID to use for S3",
|
||||
"value": ""
|
||||
},
|
||||
"AWS_SECRET_ACCESS_KEY": {
|
||||
"description": "AWS Secret Access Key to use for S3",
|
||||
"value": ""
|
||||
},
|
||||
"AWS_S3_BUCKET_NAME": {
|
||||
"description": "AWS Bucket Name to use for S3",
|
||||
"value": ""
|
||||
},
|
||||
"SENTRY_DSN": {
|
||||
"description": "",
|
||||
"value": ""
|
||||
},
|
||||
"WEB_URL": {
|
||||
"description": "Web URL for Plane this will be used for redirections in the emails",
|
||||
"value": ""
|
||||
},
|
||||
"GITHUB_CLIENT_SECRET": {
|
||||
"description": "Github Client Secret",
|
||||
"value": ""
|
||||
},
|
||||
"NEXT_PUBLIC_API_BASE_URL": {
|
||||
"description": "Next Public API Base URL",
|
||||
"value": ""
|
||||
},
|
||||
"SECRET_KEY": {
|
||||
"description": "Django Secret Key",
|
||||
"value": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ["custom"],
|
||||
rules: {
|
||||
"@next/next/no-img-element": "off",
|
||||
},
|
||||
};
|
||||
@@ -1,11 +0,0 @@
|
||||
FROM node:18-alpine
|
||||
RUN apk add --no-cache libc6-compat
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
COPY . .
|
||||
RUN yarn global add turbo
|
||||
RUN yarn install
|
||||
EXPOSE 3000
|
||||
CMD ["yarn","dev"]
|
||||
@@ -1,56 +0,0 @@
|
||||
import { EditorProps } from "@tiptap/pm/view";
|
||||
import { startImageUpload } from "./plugins/upload-image";
|
||||
|
||||
export const TiptapEditorProps: EditorProps = {
|
||||
attributes: {
|
||||
class: `prose prose-brand max-w-full prose-headings:font-display font-default focus:outline-none`,
|
||||
},
|
||||
handleDOMEvents: {
|
||||
keydown: (_view, event) => {
|
||||
// prevent default event listeners from firing when slash command is active
|
||||
if (["ArrowUp", "ArrowDown", "Enter"].includes(event.key)) {
|
||||
const slashCommand = document.querySelector("#slash-command");
|
||||
if (slashCommand) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
handlePaste: (view, event) => {
|
||||
if (
|
||||
event.clipboardData &&
|
||||
event.clipboardData.files &&
|
||||
event.clipboardData.files[0]
|
||||
) {
|
||||
event.preventDefault();
|
||||
const file = event.clipboardData.files[0];
|
||||
const pos = view.state.selection.from;
|
||||
|
||||
startImageUpload(file, view, pos);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
handleDrop: (view, event, _slice, moved) => {
|
||||
if (
|
||||
!moved &&
|
||||
event.dataTransfer &&
|
||||
event.dataTransfer.files &&
|
||||
event.dataTransfer.files[0]
|
||||
) {
|
||||
event.preventDefault();
|
||||
const file = event.dataTransfer.files[0];
|
||||
const coordinates = view.posAtCoords({
|
||||
left: event.clientX,
|
||||
top: event.clientY,
|
||||
});
|
||||
// here we deduct 1 from the pos or else the image will create an extra node
|
||||
if (coordinates) {
|
||||
startImageUpload(file, view, coordinates.pos - 1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
type IssueLabelsListProps = {
|
||||
labels?: (string | undefined)[];
|
||||
length?: number;
|
||||
showLength?: boolean;
|
||||
};
|
||||
|
||||
export const IssueLabelsList: React.FC<IssueLabelsListProps> = ({
|
||||
labels,
|
||||
length = 5,
|
||||
showLength = true,
|
||||
}) => (
|
||||
<>
|
||||
{labels && (
|
||||
<>
|
||||
{labels.slice(0, length).map((color, index) => (
|
||||
<div className={`flex h-4 w-4 rounded-full ${index ? "-ml-3.5" : ""}`}>
|
||||
<span
|
||||
className={`h-4 w-4 flex-shrink-0 rounded-full border border-custom-border-200
|
||||
`}
|
||||
style={{
|
||||
backgroundColor: color && color !== "" ? color : "#000000",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
{labels.length > length ? <span>+{labels.length - length}</span> : null}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
@@ -1,35 +0,0 @@
|
||||
// icons
|
||||
import { Bars3Icon } from "@heroicons/react/24/outline";
|
||||
|
||||
type Props = {
|
||||
breadcrumbs?: JSX.Element;
|
||||
left?: JSX.Element;
|
||||
right?: JSX.Element;
|
||||
setToggleSidebar: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
noHeader: boolean;
|
||||
};
|
||||
|
||||
const Header: React.FC<Props> = ({ breadcrumbs, left, right, setToggleSidebar, noHeader }) => (
|
||||
<div
|
||||
className={`relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 px-5 py-4 ${
|
||||
noHeader ? "md:hidden" : ""
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
|
||||
<div className="block md:hidden">
|
||||
<button
|
||||
type="button"
|
||||
className="grid h-8 w-8 place-items-center rounded border border-custom-border-200"
|
||||
onClick={() => setToggleSidebar((prevData) => !prevData)}
|
||||
>
|
||||
<Bars3Icon className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
{breadcrumbs}
|
||||
<div className="flex-shrink-0">{left}</div>
|
||||
</div>
|
||||
<div className="flex-shrink-0">{right}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Header;
|
||||
@@ -1,3 +0,0 @@
|
||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.3704 21.7617C11.349 21.5892 9.64994 20.7655 8.27325 19.2907C6.89653 17.816 6.20817 16.0515 6.20817 13.9974C6.20817 11.8239 6.96342 9.98197 8.47392 8.47148C9.98441 6.96098 11.8264 6.20573 13.9998 6.20573C16.0539 6.20573 17.8184 6.89181 19.2932 8.26397C20.768 9.63613 21.5916 11.3402 21.7642 13.3762L20.0506 12.8449C19.7825 11.3974 19.0895 10.1969 17.9714 9.24335C16.8532 8.28984 15.5294 7.81308 13.9998 7.81308C12.2835 7.81308 10.8237 8.4147 9.62039 9.61795C8.41714 10.8212 7.81552 12.281 7.81552 13.9974C7.81552 15.5157 8.29456 16.8389 9.25262 17.9668C10.2107 19.0946 11.4089 19.793 12.8474 20.0618L13.3704 21.7617ZM13.9998 27.4557C12.1381 27.4557 10.3885 27.1024 8.75109 26.396C7.11366 25.6896 5.68932 24.7308 4.47807 23.5198C3.26682 22.3088 2.30791 20.8847 1.60135 19.2475C0.894785 17.6104 0.541504 15.8611 0.541504 13.9997C0.541504 12.1383 0.894726 10.3887 1.60117 8.75084C2.30762 7.11296 3.26634 5.68825 4.47736 4.47669C5.6884 3.26511 7.11249 2.30594 8.74964 1.59919C10.3868 0.89244 12.136 0.539062 13.9974 0.539062C15.8588 0.539062 17.6085 0.892345 19.2463 1.59891C20.8842 2.30547 22.3089 3.26438 23.5205 4.47563C24.7321 5.68688 25.6912 7.11122 26.398 8.74865C27.1047 10.3861 27.4581 12.1356 27.4581 13.9974C27.4581 14.1735 27.4545 14.3497 27.4472 14.5259C27.4399 14.702 27.4245 14.8782 27.4009 15.0544L25.8508 14.5804V13.9974C25.8508 10.6991 24.7002 7.89934 22.399 5.59816C20.0978 3.297 17.2981 2.14641 13.9998 2.14641C10.7015 2.14641 7.90178 3.297 5.6006 5.59816C3.29944 7.89934 2.14885 10.6991 2.14885 13.9974C2.14885 17.2956 3.29944 20.0954 5.6006 22.3966C7.90178 24.6977 10.7015 25.8483 13.9998 25.8483H14.5828L15.0704 27.3984C14.892 27.422 14.7136 27.4375 14.5351 27.4448C14.3567 27.452 14.1783 27.4557 13.9998 27.4557ZM25.156 27.284L19.1079 21.2223L18.0999 24.2953C18.0033 24.555 17.8256 24.679 17.567 24.6672C17.3084 24.6554 17.1437 24.5196 17.0728 24.2599L14.2831 14.94C14.2214 14.7366 14.265 14.5604 14.4139 14.4115C14.5628 14.2625 14.739 14.219 14.9424 14.2807L24.2759 17.0704C24.5266 17.1412 24.6578 17.306 24.6696 17.5646C24.6814 17.8232 24.562 17.9963 24.3113 18.0838L21.2247 19.1055L27.2864 25.1536C27.3954 25.2592 27.4499 25.3837 27.4499 25.527C27.4499 25.6704 27.3954 25.7965 27.2864 25.9055L25.9079 27.284C25.8023 27.393 25.6778 27.4474 25.5344 27.4474C25.3911 27.4474 25.265 27.393 25.156 27.284Z" fill="#9FBBFF"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.4 KiB |
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"extends": "tsconfig/nextjs.json",
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules"],
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"jsx": "preserve"
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
|
||||
const nextConfig = {
|
||||
experimental: {
|
||||
appDir: true,
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
@@ -1,4 +0,0 @@
|
||||
# Deploy the Plane image
|
||||
FROM makeplane/plane
|
||||
|
||||
LABEL maintainer="engineering@plane.so"
|
||||
@@ -38,11 +38,12 @@ services:
|
||||
container_name: planefrontend
|
||||
image: makeplane/plane-frontend:latest
|
||||
restart: always
|
||||
command: /usr/local/bin/start.sh
|
||||
command: /usr/local/bin/start.sh apps/app/server.js app
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
NEXT_PUBLIC_API_BASE_URL: ${NEXT_PUBLIC_API_BASE_URL}
|
||||
NEXT_PUBLIC_DEPLOY_URL: ${NEXT_PUBLIC_DEPLOY_URL}
|
||||
NEXT_PUBLIC_GOOGLE_CLIENTID: "0"
|
||||
NEXT_PUBLIC_GITHUB_APP_NAME: "0"
|
||||
NEXT_PUBLIC_GITHUB_ID: "0"
|
||||
|
||||
@@ -41,12 +41,14 @@ services:
|
||||
dockerfile: ./apps/app/Dockerfile.web
|
||||
args:
|
||||
NEXT_PUBLIC_API_BASE_URL: http://localhost:8000
|
||||
NEXT_PUBLIC_DEPLOY_URL: http://localhost/spaces
|
||||
restart: always
|
||||
command: /usr/local/bin/start.sh
|
||||
command: /usr/local/bin/start.sh apps/app/server.js app
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
NEXT_PUBLIC_API_BASE_URL: ${NEXT_PUBLIC_API_BASE_URL}
|
||||
NEXT_PUBLIC_DEPLOY_URL: ${NEXT_PUBLIC_DEPLOY_URL}
|
||||
NEXT_PUBLIC_GOOGLE_CLIENTID: "0"
|
||||
NEXT_PUBLIC_GITHUB_APP_NAME: "0"
|
||||
NEXT_PUBLIC_GITHUB_ID: "0"
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
build:
|
||||
docker:
|
||||
web: deploy/heroku/Dockerfile
|
||||
@@ -18,6 +18,11 @@ server {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
location /space/ {
|
||||
proxy_pass http://localhost:4000/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
|
||||
14
package.json
14
package.json
@@ -1,22 +1,16 @@
|
||||
{
|
||||
"name": "plane",
|
||||
"repository": "https://github.com/makeplane/plane.git",
|
||||
"license": "AGPL-3.0",
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"apps/*",
|
||||
"web/",
|
||||
"space/",
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "turbo run build",
|
||||
"dev": "turbo run dev",
|
||||
"start": "turbo run start",
|
||||
"lint": "turbo run lint",
|
||||
"clean": "turbo run clean"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint-config-custom": "*",
|
||||
"prettier": "latest",
|
||||
"turbo": "latest"
|
||||
"prettier": "latest"
|
||||
},
|
||||
"packageManager": "yarn@1.22.19"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/bin/sh
|
||||
FROM=$1
|
||||
TO=$2
|
||||
DIRECTORY=$3
|
||||
|
||||
if [ "${FROM}" = "${TO}" ]; then
|
||||
echo "Nothing to replace, the value is already set to ${TO}."
|
||||
@@ -11,4 +12,4 @@ fi
|
||||
# Only perform action if $FROM and $TO are different.
|
||||
echo "Replacing all statically built instances of $FROM with this string $TO ."
|
||||
|
||||
grep -R -la "${FROM}" apps/app/.next | xargs -I{} sed -i "s|$FROM|$TO|g" "{}"
|
||||
grep -R -la "${FROM}" apps/$DIRECTORY/.next | xargs -I{} sed -i "s|$FROM|$TO|g" "{}"
|
||||
|
||||
13
setup.sh
13
setup.sh
@@ -14,3 +14,16 @@ echo -e "SECRET_KEY=\"$(tr -dc 'a-z0-9' < /dev/urandom | head -c50)\"" >> ./.en
|
||||
|
||||
# WEB_URL for email redirection and image saving
|
||||
echo -e "WEB_URL=$1" >> ./.env
|
||||
|
||||
# Generate Prompt for taking tiptap auth key
|
||||
echo -e "\n\e[1;38m Instructions for generating TipTap Pro Extensions Auth Token \e[0m \n"
|
||||
|
||||
echo -e "\e[1;38m 1. Head over to TipTap cloud's Pro Extensions Page, https://collab.tiptap.dev/pro-extensions \e[0m"
|
||||
echo -e "\e[1;38m 2. Copy the token given to you under the first paragraph, after 'Here it is' \e[0m \n"
|
||||
|
||||
read -p $'\e[1;32m Please Enter Your TipTap Pro Extensions Authentication Token: \e[0m \e[1;36m' authToken
|
||||
|
||||
|
||||
echo "@tiptap-pro:registry=https://registry.tiptap.dev/
|
||||
//registry.tiptap.dev/:_authToken=${authToken}" > .npmrc
|
||||
|
||||
|
||||
70
space/Dockerfile.legacy
Normal file
70
space/Dockerfile.legacy
Normal file
@@ -0,0 +1,70 @@
|
||||
FROM node:18-alpine AS builder
|
||||
RUN apk add --no-cache libc6-compat
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
ENV NEXT_PUBLIC_API_BASE_URL=http://NEXT_PUBLIC_API_BASE_URL_PLACEHOLDER
|
||||
|
||||
RUN yarn global add turbo
|
||||
COPY . .
|
||||
|
||||
RUN turbo prune --scope=space --docker
|
||||
|
||||
# Add lockfile and package.json's of isolated subworkspace
|
||||
FROM node:18-alpine AS installer
|
||||
|
||||
RUN apk add --no-cache libc6-compat
|
||||
WORKDIR /app
|
||||
ARG NEXT_PUBLIC_API_BASE_URL=http://localhost:8000
|
||||
|
||||
# First install the dependencies (as they change less often)
|
||||
COPY .gitignore .gitignore
|
||||
COPY --from=builder /app/out/json/ .
|
||||
COPY --from=builder /app/out/yarn.lock ./yarn.lock
|
||||
RUN yarn install --network-timeout 500000
|
||||
|
||||
# Build the project
|
||||
COPY --from=builder /app/out/full/ .
|
||||
COPY turbo.json turbo.json
|
||||
COPY replace-env-vars.sh /usr/local/bin/
|
||||
USER root
|
||||
RUN chmod +x /usr/local/bin/replace-env-vars.sh
|
||||
|
||||
RUN yarn turbo run build --filter=space
|
||||
|
||||
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL \
|
||||
BUILT_NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
|
||||
|
||||
RUN /usr/local/bin/replace-env-vars.sh http://NEXT_PUBLIC_WEBAPP_URL_PLACEHOLDER ${NEXT_PUBLIC_API_BASE_URL} space
|
||||
|
||||
FROM node:18-alpine AS runner
|
||||
WORKDIR /app
|
||||
|
||||
# Don't run production as root
|
||||
RUN addgroup --system --gid 1001 plane
|
||||
RUN adduser --system --uid 1001 captain
|
||||
USER captain
|
||||
|
||||
COPY --from=installer /app/apps/space/next.config.js .
|
||||
COPY --from=installer /app/apps/space/package.json .
|
||||
|
||||
# Automatically leverage output traces to reduce image size
|
||||
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
||||
COPY --from=installer --chown=captain:plane /app/apps/space/.next/standalone ./
|
||||
|
||||
COPY --from=installer --chown=captain:plane /app/apps/space/.next ./apps/space/.next
|
||||
|
||||
ARG NEXT_PUBLIC_API_BASE_URL=http://localhost:8000
|
||||
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL \
|
||||
BUILT_NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
|
||||
|
||||
USER root
|
||||
COPY replace-env-vars.sh /usr/local/bin/
|
||||
COPY start.sh /usr/local/bin/
|
||||
RUN chmod +x /usr/local/bin/replace-env-vars.sh
|
||||
RUN chmod +x /usr/local/bin/start.sh
|
||||
|
||||
USER captain
|
||||
|
||||
ENV NEXT_TELEMETRY_DISABLED 1
|
||||
|
||||
EXPOSE 3000
|
||||
56
space/Dockerfile.space
Normal file
56
space/Dockerfile.space
Normal file
@@ -0,0 +1,56 @@
|
||||
FROM node:18-alpine AS base
|
||||
|
||||
# Install dependencies only when needed
|
||||
FROM base AS deps
|
||||
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
|
||||
RUN apk add --no-cache libc6-compat
|
||||
WORKDIR /space
|
||||
|
||||
# Install dependencies based on the preferred package manager
|
||||
COPY package.json yarn.lock* ./
|
||||
RUN yarn
|
||||
|
||||
|
||||
# Rebuild the source code only when needed
|
||||
FROM base AS builder
|
||||
WORKDIR /space
|
||||
|
||||
COPY . .
|
||||
|
||||
COPY --from=deps /space/node_modules ./node_modules
|
||||
COPY --from=deps /space/yarn.lock .
|
||||
|
||||
ENV NEXT_TELEMETRY_DISABLED 1
|
||||
|
||||
RUN yarn build
|
||||
|
||||
# If using npm comment out above and use below instead
|
||||
# RUN npm run build
|
||||
|
||||
# Production image, copy all the files and run next
|
||||
FROM base AS runner
|
||||
WORKDIR /space
|
||||
|
||||
ENV NODE_ENV production
|
||||
# Uncomment the following line in case you want to disable telemetry during runtime.
|
||||
# ENV NEXT_TELEMETRY_DISABLED 1
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
|
||||
COPY --from=builder /space/public ./public
|
||||
|
||||
# Automatically leverage output traces to reduce image size
|
||||
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
||||
COPY --from=builder --chown=nextjs:nodejs /space/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /space/.next/static ./.next/static
|
||||
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENV PORT 3000
|
||||
# set hostname to localhost
|
||||
ENV HOSTNAME "0.0.0.0"
|
||||
|
||||
CMD ["node", "space/server.js"]
|
||||
@@ -1,11 +1,44 @@
|
||||
"use client";
|
||||
|
||||
// next imports
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { Metadata, ResolvingMetadata } from "next";
|
||||
// components
|
||||
import IssueNavbar from "components/issues/navbar";
|
||||
import IssueFilter from "components/issues/filters-render";
|
||||
// service
|
||||
import ProjectService from "services/project.service";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
type LayoutProps = {
|
||||
params: { workspace_slug: string; project_slug: string };
|
||||
};
|
||||
|
||||
export async function generateMetadata({ params }: LayoutProps): Promise<Metadata> {
|
||||
// read route params
|
||||
const { workspace_slug, project_slug } = params;
|
||||
const projectServiceInstance = new ProjectService();
|
||||
|
||||
try {
|
||||
const project = await projectServiceInstance?.getProjectSettingsAsync(workspace_slug, project_slug);
|
||||
|
||||
return {
|
||||
title: `${project?.project_details?.name} | ${workspace_slug}`,
|
||||
description: `${
|
||||
project?.project_details?.description || `${project?.project_details?.name} | ${workspace_slug}`
|
||||
}`,
|
||||
icons: `data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>${
|
||||
typeof project?.project_details?.emoji != "object"
|
||||
? String.fromCodePoint(parseInt(project?.project_details?.emoji))
|
||||
: "✈️"
|
||||
}</text></svg>`,
|
||||
};
|
||||
} catch (error: any) {
|
||||
if (error?.data?.error) {
|
||||
redirect(`/project-not-published`);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
const RootLayout = ({ children }: { children: React.ReactNode }) => (
|
||||
<div className="relative w-screen min-h-[500px] h-screen overflow-hidden flex flex-col">
|
||||
@@ -25,19 +25,48 @@ const WorkspaceProjectPage = observer(() => {
|
||||
const routerSearchparams = useSearchParams();
|
||||
|
||||
const { workspace_slug, project_slug } = routerParams as { workspace_slug: string; project_slug: string };
|
||||
const board = routerSearchparams.get("board") as TIssueBoardKeys | "";
|
||||
const board =
|
||||
routerSearchparams &&
|
||||
routerSearchparams.get("board") != null &&
|
||||
(routerSearchparams.get("board") as TIssueBoardKeys | "");
|
||||
|
||||
// updating default board view when we are in the issues page
|
||||
useEffect(() => {
|
||||
if (workspace_slug && project_slug) {
|
||||
if (!board) {
|
||||
store.issue.setCurrentIssueBoardView("list");
|
||||
router.replace(`/${workspace_slug}/${project_slug}?board=${store?.issue?.currentIssueBoardView}`);
|
||||
} else {
|
||||
if (board != store?.issue?.currentIssueBoardView) store.issue.setCurrentIssueBoardView(board);
|
||||
if (workspace_slug && project_slug && store?.project?.workspaceProjectSettings) {
|
||||
const workspacePRojectSettingViews = store?.project?.workspaceProjectSettings?.views;
|
||||
const userAccessViews: TIssueBoardKeys[] = [];
|
||||
|
||||
Object.keys(workspacePRojectSettingViews).filter((_key) => {
|
||||
if (_key === "list" && workspacePRojectSettingViews.list === true) userAccessViews.push(_key);
|
||||
if (_key === "kanban" && workspacePRojectSettingViews.kanban === true) userAccessViews.push(_key);
|
||||
if (_key === "calendar" && workspacePRojectSettingViews.calendar === true) userAccessViews.push(_key);
|
||||
if (_key === "spreadsheet" && workspacePRojectSettingViews.spreadsheet === true) userAccessViews.push(_key);
|
||||
if (_key === "gantt" && workspacePRojectSettingViews.gantt === true) userAccessViews.push(_key);
|
||||
});
|
||||
|
||||
if (userAccessViews && userAccessViews.length > 0) {
|
||||
if (!board) {
|
||||
store.issue.setCurrentIssueBoardView(userAccessViews[0]);
|
||||
router.replace(`/${workspace_slug}/${project_slug}?board=${userAccessViews[0]}`);
|
||||
} else {
|
||||
if (userAccessViews.includes(board)) {
|
||||
if (store.issue.currentIssueBoardView === null) store.issue.setCurrentIssueBoardView(board);
|
||||
else {
|
||||
if (board === store.issue.currentIssueBoardView)
|
||||
router.replace(`/${workspace_slug}/${project_slug}?board=${board}`);
|
||||
else {
|
||||
store.issue.setCurrentIssueBoardView(board);
|
||||
router.replace(`/${workspace_slug}/${project_slug}?board=${board}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
store.issue.setCurrentIssueBoardView(userAccessViews[0]);
|
||||
router.replace(`/${workspace_slug}/${project_slug}?board=${userAccessViews[0]}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [workspace_slug, project_slug, board, router, store?.issue]);
|
||||
}, [workspace_slug, project_slug, board, router, store?.issue, store?.project?.workspaceProjectSettings]);
|
||||
|
||||
useEffect(() => {
|
||||
if (workspace_slug && project_slug) {
|
||||
@@ -3,7 +3,7 @@
|
||||
import React from "react";
|
||||
|
||||
const HomePage = () => (
|
||||
<div className="relative w-screen h-screen flex justify-center items-center text-5xl">Plane Space</div>
|
||||
<div className="relative w-screen h-screen flex justify-center items-center text-5xl">Plane Deploy</div>
|
||||
);
|
||||
|
||||
export default HomePage;
|
||||
31
space/app/project-not-published/page.tsx
Normal file
31
space/app/project-not-published/page.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
// next imports
|
||||
import Image from "next/image";
|
||||
|
||||
const CustomProjectNotPublishedError = () => (
|
||||
<div className="relative w-screen min-h-screen h-full flex justify-center items-center py-5">
|
||||
<div className="max-w-[700px] space-y-5">
|
||||
<div className="flex items-center flex-col gap-3 text-center">
|
||||
<div className="relative w-[240px] h-[240px]">
|
||||
<Image src={`/project-not-published.svg`} layout="fill" alt="404- Page not found" />
|
||||
</div>
|
||||
<div className="text-xl font-medium">
|
||||
Oops! The page you{`'`}re looking for isn{`'`}t live at the moment.
|
||||
</div>
|
||||
<div className="text-sm text-custom-text-200">
|
||||
If this is your project, login to your workspace to adjust its visibility settings and make it public.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center flex justify-center items-center">
|
||||
<a
|
||||
href={`https://app.plane.so/`}
|
||||
className="transition-all border border-gray-200 bg-gray-50 hover:bg-gray-100 text-gray-700 hover:text-gray-800 cursor-pointer p-1.5 px-2.5 rounded-sm text-sm font-medium hover:scale-105 select-none"
|
||||
>
|
||||
Go to your Workspace
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default CustomProjectNotPublishedError;
|
||||
@@ -27,7 +27,7 @@ export const IssueListBlock = ({ issue }: { issue: IIssue }) => {
|
||||
<div className="font-medium text-gray-800 h-full line-clamp-2">{issue.name}</div>
|
||||
|
||||
{/* priority */}
|
||||
<div className="relative flex items-center gap-3 w-full">
|
||||
<div className="relative flex flex-wrap items-center gap-2 w-full">
|
||||
{issue?.priority && (
|
||||
<div className="flex-shrink-0">
|
||||
<IssueBlockPriority priority={issue?.priority} />
|
||||
@@ -1,5 +1,7 @@
|
||||
"use client";
|
||||
|
||||
// next imports
|
||||
import Image from "next/image";
|
||||
// components
|
||||
import { NavbarSearch } from "./search";
|
||||
import { NavbarIssueBoardView } from "./issue-board-view";
|
||||
@@ -12,6 +14,18 @@ import { observer } from "mobx-react-lite";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
const renderEmoji = (emoji: string | { name: string; color: string }) => {
|
||||
if (!emoji) return;
|
||||
|
||||
if (typeof emoji === "object")
|
||||
return (
|
||||
<span style={{ color: emoji.color }} className="material-symbols-rounded text-lg">
|
||||
{emoji.name}
|
||||
</span>
|
||||
);
|
||||
else return isNaN(parseInt(emoji)) ? emoji : String.fromCodePoint(parseInt(emoji));
|
||||
};
|
||||
|
||||
const IssueNavbar = observer(() => {
|
||||
const store: RootStore = useMobxStore();
|
||||
|
||||
@@ -20,7 +34,11 @@ const IssueNavbar = observer(() => {
|
||||
{/* project detail */}
|
||||
<div className="flex-shrink-0 flex items-center gap-2">
|
||||
<div className="w-[32px] h-[32px] rounded-sm flex justify-center items-center bg-gray-100 text-[24px]">
|
||||
{store?.project?.project && store?.project?.project?.icon ? store?.project?.project?.icon : "😊"}
|
||||
{store?.project?.project && store?.project?.project?.emoji ? (
|
||||
renderEmoji(store?.project?.project?.emoji)
|
||||
) : (
|
||||
<Image src="/plane-logo.webp" alt="plane logo" className="w-[24px] h-[24px]" height="24" width="24" />
|
||||
)}
|
||||
</div>
|
||||
<div className="font-medium text-lg max-w-[300px] line-clamp-1 overflow-hidden">
|
||||
{store?.project?.project?.name || `...`}
|
||||
@@ -1,10 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
// next imports
|
||||
import { useSearchParams } from "next/navigation";
|
||||
// interface
|
||||
import { TIssueBoardKeys } from "store/types";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
@@ -12,11 +8,6 @@ import { RootStore } from "store/root";
|
||||
const MobxStoreInit = () => {
|
||||
const store: RootStore = useMobxStore();
|
||||
|
||||
// search params
|
||||
const routerSearchparams = useSearchParams();
|
||||
|
||||
const board = routerSearchparams.get("board") as TIssueBoardKeys;
|
||||
|
||||
useEffect(() => {
|
||||
// theme
|
||||
const _theme = localStorage && localStorage.getItem("app_theme") ? localStorage.getItem("app_theme") : "light";
|
||||
@@ -24,11 +15,6 @@ const MobxStoreInit = () => {
|
||||
else localStorage.setItem("app_theme", _theme && _theme != "light" ? "dark" : "light");
|
||||
}, [store?.theme]);
|
||||
|
||||
// updating default board view when we are in the issues page
|
||||
useEffect(() => {
|
||||
if (board && board != store?.issue?.currentIssueBoardView) store.issue.setCurrentIssueBoardView(board);
|
||||
}, [board, store?.issue]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
|
||||
14
space/next.config.js
Normal file
14
space/next.config.js
Normal file
@@ -0,0 +1,14 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const path = require("path");
|
||||
|
||||
const nextConfig = {
|
||||
reactStrictMode: false,
|
||||
swcMinify: true,
|
||||
experimental: {
|
||||
outputFileTracingRoot: path.join(__dirname, "../../"),
|
||||
appDir: true,
|
||||
},
|
||||
output: "standalone",
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "plane-space",
|
||||
"name": "space",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"dev": "next dev -p 4000",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"start": "next start -p 4000",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -20,7 +20,7 @@
|
||||
"js-cookie": "^3.0.1",
|
||||
"mobx": "^6.10.0",
|
||||
"mobx-react-lite": "^4.0.3",
|
||||
"next": "^13.4.13",
|
||||
"next": "^13.4.16",
|
||||
"nprogress": "^0.2.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
30
space/pages/404.tsx
Normal file
30
space/pages/404.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
// next imports
|
||||
import Image from "next/image";
|
||||
|
||||
const Custom404Error = () => (
|
||||
<div className="relative w-screen min-h-screen h-full flex justify-center items-center py-5">
|
||||
<div className="max-w-[700px] space-y-5">
|
||||
<div className="flex items-center flex-col gap-3 text-center">
|
||||
<div className="relative w-[240px] h-[240px]">
|
||||
<Image src={`/404.svg`} layout="fill" alt="404- Page not found" />
|
||||
</div>
|
||||
<div className="text-xl font-medium">Oops! Something went wrong.</div>
|
||||
<div className="text-sm text-custom-text-200">
|
||||
Sorry, the page you are looking for cannot be found. It may have been removed, had its name changed, or is
|
||||
temporarily unavailable.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center flex justify-center items-center">
|
||||
<a
|
||||
href={`https://app.plane.so/`}
|
||||
className="transition-all border border-gray-200 bg-gray-50 hover:bg-gray-100 text-gray-700 hover:text-gray-800 cursor-pointer p-1.5 px-2.5 rounded-sm text-sm font-medium hover:scale-105 select-none"
|
||||
>
|
||||
Go to your Workspace
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Custom404Error;
|
||||
10
space/pages/_app.tsx
Normal file
10
space/pages/_app.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
// styles
|
||||
import "styles/globals.css";
|
||||
// types
|
||||
import type { AppProps } from "next/app";
|
||||
|
||||
function MyApp({ Component, pageProps }: AppProps) {
|
||||
return <Component {...pageProps} />;
|
||||
}
|
||||
|
||||
export default MyApp;
|
||||
17
space/pages/_document.tsx
Normal file
17
space/pages/_document.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import Document, { Html, Head, Main, NextScript } from "next/document";
|
||||
|
||||
class MyDocument extends Document {
|
||||
render() {
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MyDocument;
|
||||
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 566 B After Width: | Height: | Size: 566 B |
4
space/public/project-not-published.svg
Normal file
4
space/public/project-not-published.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="219" height="219" viewBox="0 0 219 219" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="109.5" cy="109.5" r="109.5" fill="#FAFAFA"/>
|
||||
<path d="M110.423 153.361C104.339 153.361 98.6853 152.27 93.4608 150.087C88.2363 147.904 83.6917 144.88 79.827 141.016C75.9623 137.151 72.9385 132.606 70.7557 127.382C68.5728 122.157 67.4814 116.504 67.4814 110.42C67.4814 105.554 68.1971 100.973 69.6285 96.679C71.0598 92.3849 73.0638 88.4486 75.6402 84.8702L63.9388 73.1687C63.2946 72.5246 62.9905 71.7552 63.0263 70.8606C63.062 69.966 63.402 69.1967 64.0461 68.5525C64.6902 67.9084 65.4596 67.5864 66.3542 67.5864C67.2488 67.5864 68.0182 67.9084 68.6623 68.5525L150.68 150.678C151.324 151.322 151.646 152.073 151.646 152.932C151.646 153.791 151.324 154.542 150.68 155.186C150.036 155.83 149.266 156.153 148.372 156.153C147.477 156.153 146.708 155.83 146.064 155.186L135.973 145.203C132.394 147.779 128.458 149.783 124.164 151.214C119.87 152.646 115.289 153.361 110.423 153.361ZM110.423 146.92C114.359 146.92 118.08 146.366 121.587 145.256C125.094 144.147 128.351 142.59 131.356 140.586L112.355 121.585L106.343 127.597C105.699 128.241 104.948 128.581 104.089 128.617C103.23 128.652 102.478 128.348 101.834 127.704L88.7373 114.5C88.0932 113.855 87.789 113.05 87.8248 112.084C87.8606 111.118 88.2005 110.313 88.8446 109.669C89.4888 109.025 90.2939 108.703 91.2601 108.703C92.2263 108.703 93.0314 109.025 93.6755 109.669L104.089 120.189L107.524 116.754L80.2564 89.4864C78.2525 92.4922 76.6959 95.7486 75.5866 99.2555C74.4772 102.762 73.9226 106.484 73.9226 110.42C73.9226 120.798 77.4116 129.475 84.3895 136.453C91.3674 143.431 100.045 146.92 110.423 146.92ZM145.205 135.97L140.589 131.354C142.593 128.348 144.149 125.092 145.259 121.585C146.368 118.078 146.923 114.356 146.923 110.42C146.923 100.043 143.434 91.365 136.456 84.3871C129.478 77.4092 120.8 73.9202 110.423 73.9202C106.486 73.9202 102.765 74.4748 99.2579 75.5842C95.751 76.6935 92.4946 78.2501 89.4888 80.254L84.8726 75.6378C88.451 73.0614 92.3873 71.0574 96.6814 69.6261C100.976 68.1947 105.556 67.479 110.423 67.479C116.434 67.479 122.052 68.5883 127.277 70.8069C132.502 73.0256 137.046 76.0672 140.911 79.9319C144.776 83.7967 147.817 88.3413 150.036 93.5658C152.254 98.7903 153.364 104.408 153.364 110.42C153.364 115.287 152.648 119.867 151.217 124.161C149.785 128.455 147.781 132.392 145.205 135.97ZM121.695 112.353L116.756 107.522L127.17 97.1084C127.814 96.4643 128.601 96.1422 129.531 96.1422C130.462 96.1422 131.285 96.4643 132.001 97.1084C132.716 97.8241 133.074 98.6471 133.074 99.5775C133.074 100.508 132.716 101.331 132.001 102.047L121.695 112.353Z" fill="#B9B9B9"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -3,14 +3,12 @@ import axios from "axios";
|
||||
// js cookie
|
||||
import Cookies from "js-cookie";
|
||||
|
||||
const base_url: string | null = "https://boarding.plane.so";
|
||||
|
||||
abstract class APIService {
|
||||
protected baseURL: string;
|
||||
protected headers: any = {};
|
||||
|
||||
constructor(baseURL: string) {
|
||||
this.baseURL = base_url ? base_url : baseURL;
|
||||
constructor(_baseURL: string) {
|
||||
this.baseURL = _baseURL;
|
||||
}
|
||||
|
||||
setRefreshToken(token: string) {
|
||||
@@ -1,11 +1,9 @@
|
||||
// services
|
||||
import APIService from "services/api.service";
|
||||
|
||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||
|
||||
class IssueService extends APIService {
|
||||
constructor() {
|
||||
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
super(process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
}
|
||||
|
||||
async getPublicIssues(workspace_slug: string, project_slug: string): Promise<any> {
|
||||
@@ -1,11 +1,9 @@
|
||||
// services
|
||||
import APIService from "services/api.service";
|
||||
|
||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||
|
||||
class ProjectService extends APIService {
|
||||
constructor() {
|
||||
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
super(process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
}
|
||||
|
||||
async getProjectSettingsAsync(workspace_slug: string, project_slug: string): Promise<any> {
|
||||
@@ -1,11 +1,9 @@
|
||||
// services
|
||||
import APIService from "services/api.service";
|
||||
|
||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||
|
||||
class UserService extends APIService {
|
||||
constructor() {
|
||||
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
super(process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||
}
|
||||
|
||||
async currentUser(): Promise<any> {
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user