From 33db34c3d03336a66e24ad8631e2415fc0b07ca3 Mon Sep 17 00:00:00 2001
From: Izan Gil <66965250+SrIzan10@users.noreply.github.com>
Date: Sat, 21 Dec 2024 00:45:36 +0100
Subject: [PATCH] feat: github issue creation
---
package.json | 1 +
src/app/(protected)/project/[id]/page.tsx | 5 +-
.../GithubIssueCreate/GithubIssueCreate.tsx | 72 +++++++++++++++++++
.../app/UniversalForm/UniversalForm.tsx | 27 ++++---
src/components/app/UniversalForm/types.ts | 3 +-
src/components/app/UniversalForm/zod.ts | 7 ++
src/components/ui/checkbox.tsx | 30 ++++++++
src/components/ui/textarea.tsx | 22 ++++++
src/lib/forms/actions.ts | 56 ++++++++++++++-
yarn.lock | 19 +++++
10 files changed, 229 insertions(+), 13 deletions(-)
create mode 100644 src/components/app/GithubIssueCreate/GithubIssueCreate.tsx
create mode 100644 src/components/ui/checkbox.tsx
create mode 100644 src/components/ui/textarea.tsx
diff --git a/package.json b/package.json
index f7025c2..e2fd004 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
"@octokit/core": "^6.1.2",
"@prisma/client": "^6.0.1",
"@radix-ui/react-avatar": "^1.0.4",
+ "@radix-ui/react-checkbox": "^1.1.3",
"@radix-ui/react-dialog": "^1.1.4",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-label": "^2.1.1",
diff --git a/src/app/(protected)/project/[id]/page.tsx b/src/app/(protected)/project/[id]/page.tsx
index 6c10a9e..7d0247d 100644
--- a/src/app/(protected)/project/[id]/page.tsx
+++ b/src/app/(protected)/project/[id]/page.tsx
@@ -21,6 +21,7 @@ import {
} from '@/components/ui/breadcrumb';
import { Eye, Github } from 'lucide-react';
import FeedbackView from '@/components/app/FeedbackView/FeedbackView';
+import GithubIssueCreate from '@/components/app/GithubIssueCreate/GithubIssueCreate';
// TODO: refactor to maybe append the no feedback message to the table div
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
@@ -100,9 +101,7 @@ export default async function Page({ params }: { params: Promise<{ id: string }>
{project.github && (
-
+
)}
diff --git a/src/components/app/GithubIssueCreate/GithubIssueCreate.tsx b/src/components/app/GithubIssueCreate/GithubIssueCreate.tsx
new file mode 100644
index 0000000..ed0b485
--- /dev/null
+++ b/src/components/app/GithubIssueCreate/GithubIssueCreate.tsx
@@ -0,0 +1,72 @@
+'use client';
+
+import { Button } from '@/components/ui/button';
+import { Checkbox } from '@/components/ui/checkbox';
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from '@/components/ui/dialog';
+import type { Feedback, Project } from '@prisma/client';
+import { Github } from 'lucide-react';
+import { UniversalForm } from '../UniversalForm/UniversalForm';
+import { githubCreateIssue } from '@/lib/forms/actions';
+import React from 'react';
+
+export default function GithubIssueCreate(props: Props) {
+ const [open, setOpen] = React.useState(false);
+ return (
+
+ );
+}
+
+interface Props {
+ project: Project;
+ feedback: Feedback;
+}
diff --git a/src/components/app/UniversalForm/UniversalForm.tsx b/src/components/app/UniversalForm/UniversalForm.tsx
index 08e24b6..9329b81 100644
--- a/src/components/app/UniversalForm/UniversalForm.tsx
+++ b/src/components/app/UniversalForm/UniversalForm.tsx
@@ -17,6 +17,7 @@ import { z } from 'zod';
import type { UniversalFormProps } from './types';
import {
customDataSchema,
+ githubIssueCreateSchema,
githubSettingsSchema,
githubTestIssueSchema,
projectSettingsSchema,
@@ -27,6 +28,7 @@ import { useActionState } from 'react';
import React from 'react';
import { toast } from 'sonner';
import { createSchema } from '@/lib/forms/zod';
+import { Textarea } from '@/components/ui/textarea';
export const schemaDb = [
{ name: 'projectSettings', zod: projectSettingsSchema },
@@ -35,6 +37,7 @@ export const schemaDb = [
{ name: 'create', zod: createSchema },
{ name: 'githubSettings', zod: githubSettingsSchema },
{ name: 'githubTestIssue', zod: githubTestIssueSchema },
+ { name: 'githubIssueCreate', zod: githubIssueCreateSchema },
] as const;
export function UniversalForm({
@@ -58,7 +61,7 @@ export function UniversalForm({
const initialValues = React.useMemo(() => {
const values: Record = {};
fields.forEach((field) => {
- values[field.name] = field.value ?? ''; // Use empty string as fallback
+ values[field.name] = field.value ?? ''; // Use empty string as fallback
});
return { ...values, ...defaultValues };
}, [fields, defaultValues]);
@@ -89,12 +92,20 @@ export function UniversalForm({
{field.type !== 'hidden' && {field.label}}
-
+ {field.textArea ? (
+
+ ) : (
+
+ )}
{field.description && {field.description}}
@@ -106,4 +117,4 @@ export function UniversalForm({
);
-}
\ No newline at end of file
+}
diff --git a/src/components/app/UniversalForm/types.ts b/src/components/app/UniversalForm/types.ts
index 2bdaf50..c1ef50c 100644
--- a/src/components/app/UniversalForm/types.ts
+++ b/src/components/app/UniversalForm/types.ts
@@ -9,11 +9,12 @@ export type FormFieldConfig = {
placeholder?: string;
description?: string;
value?: string;
+ textArea?: boolean;
};
export type UniversalFormProps = {
fields: FormFieldConfig[];
- schemaName: typeof schemaDb[number]['name'];
+ schemaName: (typeof schemaDb)[number]['name'];
action: (prev: any, formData: FormData) => void;
onActionComplete?: (result: unknown) => void;
defaultValues?: Partial>;
diff --git a/src/components/app/UniversalForm/zod.ts b/src/components/app/UniversalForm/zod.ts
index d7ee634..b013301 100644
--- a/src/components/app/UniversalForm/zod.ts
+++ b/src/components/app/UniversalForm/zod.ts
@@ -36,4 +36,11 @@ export const githubSettingsSchema = z.object({
export const githubTestIssueSchema = z.object({
id: z.string().nonempty(),
+});
+
+export const githubIssueCreateSchema = z.object({
+ feedback: z.string().nonempty(),
+ project: z.string().nonempty(),
+ message: z.string().nonempty(),
+ title: z.string().nonempty(),
});
\ No newline at end of file
diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx
new file mode 100644
index 0000000..df61a13
--- /dev/null
+++ b/src/components/ui/checkbox.tsx
@@ -0,0 +1,30 @@
+"use client"
+
+import * as React from "react"
+import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
+import { Check } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Checkbox = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+
+
+))
+Checkbox.displayName = CheckboxPrimitive.Root.displayName
+
+export { Checkbox }
diff --git a/src/components/ui/textarea.tsx b/src/components/ui/textarea.tsx
new file mode 100644
index 0000000..4d858bb
--- /dev/null
+++ b/src/components/ui/textarea.tsx
@@ -0,0 +1,22 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+const Textarea = React.forwardRef<
+ HTMLTextAreaElement,
+ React.ComponentProps<"textarea">
+>(({ className, ...props }, ref) => {
+ return (
+
+ )
+})
+Textarea.displayName = "Textarea"
+
+export { Textarea }
diff --git a/src/lib/forms/actions.ts b/src/lib/forms/actions.ts
index 739d747..5564043 100644
--- a/src/lib/forms/actions.ts
+++ b/src/lib/forms/actions.ts
@@ -2,6 +2,7 @@
import {
customDataSchema,
+ githubIssueCreateSchema,
githubSettingsSchema,
githubTestIssueSchema,
projectSettingsSchema,
@@ -11,7 +12,6 @@ import { validateRequest } from '../auth';
import prisma from '../db';
import zodVerify from '../zodVerify';
import { createSchema } from './zod';
-import { Octokit } from '@octokit/core';
import { octokitApp } from '../octokitApp';
export async function create(prev: any, formData: FormData) {
@@ -183,3 +183,57 @@ export async function githubTestIssue(prev: any, formData: FormData) {
return { success: true };
}
+
+export async function githubCreateIssue(prev: any, formData: FormData) {
+ const zod = await zodVerify(githubIssueCreateSchema, formData);
+ const { user } = await validateRequest();
+ if (!user) {
+ return { success: false, error: 'You must be logged in' };
+ }
+ if (!zod.success) {
+ return zod;
+ }
+
+ const project = await prisma.project.findUnique({
+ where: {
+ id: zod.data.project,
+ },
+ include: {
+ user: true,
+ },
+ });
+ if (!project) {
+ return { success: false, error: 'Project not found' };
+ }
+
+ try {
+ const [owner, repo] = project.github!.split('/').slice(-2);
+
+ for (const installationId of project.user.installations) {
+ const installation = await octokitApp.getInstallationOctokit(Number(installationId));
+ const getRepo = await installation
+ .request('GET /repos/{owner}/{repo}', {
+ owner,
+ repo,
+ })
+ .catch(() => ({ status: 404 }));
+ if (getRepo.status === 200) {
+ const createIssue = await installation.request('POST /repos/{owner}/{repo}/issues', {
+ owner,
+ repo,
+ title: zod.data.title,
+ body: `### Feedback\n\nFeedback ID: ${zod.data.feedback}\n\n### Message\n\n${zod.data.message}`,
+ });
+
+ if (createIssue.status === 201) {
+ break;
+ }
+ }
+ }
+ } catch (e) {
+ console.error(e);
+ return { success: false, error: e };
+ }
+
+ return { success: true };
+}
diff --git a/yarn.lock b/yarn.lock
index 08d1ae7..27c9fc4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1223,6 +1223,20 @@
"@radix-ui/react-use-callback-ref" "1.0.1"
"@radix-ui/react-use-layout-effect" "1.0.1"
+"@radix-ui/react-checkbox@^1.1.3":
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-checkbox/-/react-checkbox-1.1.3.tgz#0e2ab913fddf3c88603625f7a9457d73882c8a32"
+ integrity sha512-HD7/ocp8f1B3e6OHygH0n7ZKjONkhciy1Nh0yuBgObqThc3oyx+vuMfFHKAknXRHHWVE9XvXStxJFyjUmB8PIw==
+ dependencies:
+ "@radix-ui/primitive" "1.1.1"
+ "@radix-ui/react-compose-refs" "1.1.1"
+ "@radix-ui/react-context" "1.1.1"
+ "@radix-ui/react-presence" "1.1.2"
+ "@radix-ui/react-primitive" "2.0.1"
+ "@radix-ui/react-use-controllable-state" "1.1.0"
+ "@radix-ui/react-use-previous" "1.1.0"
+ "@radix-ui/react-use-size" "1.1.0"
+
"@radix-ui/react-collection@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.1.0.tgz#f18af78e46454a2360d103c2251773028b7724ed"
@@ -1700,6 +1714,11 @@
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz#3c2c8ce04827b26a39e442ff4888d9212268bd27"
integrity sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==
+"@radix-ui/react-use-previous@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz#d4dd37b05520f1d996a384eb469320c2ada8377c"
+ integrity sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==
+
"@radix-ui/react-use-rect@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz#13b25b913bd3e3987cc9b073a1a164bb1cf47b88"