Skip to content

Commit

Permalink
Add form to create the actual issue
Browse files Browse the repository at this point in the history
  • Loading branch information
sdorra committed Nov 11, 2021
1 parent 1d1ab75 commit 0e3bb86
Show file tree
Hide file tree
Showing 7 changed files with 223 additions and 41 deletions.
36 changes: 17 additions & 19 deletions src/editor/CreateConnection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,31 +19,29 @@ const CreateConnection: FC<Props> = ({ setConnection, error, isLoading }) => {
} = useForm<Connection>();

return (
<div tw="p-8 max-w-md">
<form onSubmit={handleSubmit(setConnection)}>
<h2 tw="text-2xl font-bold">Create Redmine Connection</h2>
{error ? (
<p tw="text-red-700">
<strong>Error</strong> {error.message}
</p>
) : null}
<form onSubmit={handleSubmit(setConnection)}>
<div tw="mt-8 grid grid-cols-1 gap-6">
<InputField
label="URL"
{...register("url", { required: true })}
error={errors.url && "Url is required"}
/>
<InputField
label="API Key"
{...register("apiKey", { required: true })}
error={errors.apiKey && "API Key is required"}
/>
<Button type="submit" isLoading={isLoading}>
Save
</Button>
</div>
</form>
</div>
<div tw="mt-8 grid grid-cols-1 gap-6">
<InputField
label="URL"
{...register("url", { required: true })}
error={errors.url && "Url is required"}
/>
<InputField
label="API Key"
{...register("apiKey", { required: true })}
error={errors.apiKey && "API Key is required"}
/>
<Button type="submit" isLoading={isLoading}>
Save
</Button>
</div>
</form>
);
};

Expand Down
45 changes: 45 additions & 0 deletions src/editor/CreateIssue.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React, { FC } from "react";
import { useForm } from "react-hook-form";
import useCreateIssue, { Issue } from "./useCreateIssue";
import { Screenshot } from "./Screenshot";
import InputField from "./InputField";
import Button from "./Button";

type Props = {
screenshot: Screenshot;
};

const CreateIssue: FC<Props> = ({ screenshot }) => {
const { create, isLoading, error } = useCreateIssue();
const {
register,
handleSubmit,
formState: { errors },
} = useForm<Issue>();
return (
<form onSubmit={handleSubmit((issue) => create(issue, screenshot))}>
<h2 tw="text-2xl font-bold">Create Issue</h2>
{error ? (
<p tw="text-red-700">
<strong>Error</strong> {error.message}
</p>
) : null}
<div tw="mt-8 grid grid-cols-1 gap-6">
<InputField
label="Subject"
{...register("subject", { required: true })}
error={errors.subject ? "Subject is required" : null}
/>
<label tw="block">
<span tw="text-gray-700">Description</span>
<textarea tw="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" {...register("description")}></textarea>
</label>
<Button type="submit" isLoading={isLoading}>
Save
</Button>
</div>
</form>
);
};

export default CreateIssue;
40 changes: 29 additions & 11 deletions src/editor/Editor.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,35 @@
import React, { FC } from "react";
import React, { FC, MutableRefObject, useRef } from "react";
import ImageEditor from "./ImageEditor";
import IssueEditor from "./IssueEditor";
import "twin.macro";
import { Screenshot } from "./Screenshot";

const Editor: FC = () => (
<div tw="flex flex-row flex-wrap h-screen">
<main tw="w-3/4 h-full shadow-md border-2">
<ImageEditor />
</main>
<aside tw="w-1/4 h-full">
<IssueEditor />
</aside>
</div>
);
const createScreenshot = (stageRef: MutableRefObject<any>): Screenshot => {
return {
toBlob: () => {
const canvas = stageRef.current.clearAndToCanvas({
pixelRatio: stageRef.current._pixelRatio,
}) as HTMLCanvasElement;
return new Promise<Blob>((resolve) => {
canvas.toBlob(resolve);
});
},
};
};

const Editor: FC = () => {
const stageRef = useRef<unknown>(null);
const screenshot = createScreenshot(stageRef);
return (
<div tw="flex flex-row flex-wrap h-screen">
<main tw="w-3/4 h-full shadow-md border-2">
<ImageEditor stageRef={stageRef} />
</main>
<aside tw="w-1/4 h-full">
<IssueEditor screenshot={screenshot} />
</aside>
</div>
);
};

export default Editor;
9 changes: 6 additions & 3 deletions src/editor/ImageEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import React, { FC, useRef } from "react";
import React, { FC, MutableRefObject, useRef } from "react";
import ReactImgEditor from "react-img-editor";
import "react-img-editor/assets/index.css";
import useBugShot from "./useBugShot";
import "twin.macro";
import useDimension from "./useDimension";

const ImageEditor: FC = () => {
type Props = {
stageRef: MutableRefObject<unknown>;
};

const ImageEditor: FC<Props> = ({ stageRef }) => {
const image = useBugShot();
const stageRef = useRef<any>(null);
const { ref, width, height } = useDimension<HTMLDivElement>();

const setStage = (stage: any) => {
Expand Down
24 changes: 16 additions & 8 deletions src/editor/IssueEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import React from "react";
import React, { FC } from "react";
import useConnection from "./useConnection";
import CreateConnection from "./CreateConnection";
import { Screenshot } from "./Screenshot";
import CreateIssue from "./CreateIssue";
import "twin.macro";

const IssueEditor = () => {
type Props = {
screenshot: Screenshot;
};

const Editor: FC<Props> = ({screenshot}) => {
const { connection, isLoading, update } = useConnection();

if (isLoading) {
Expand All @@ -13,12 +20,13 @@ const IssueEditor = () => {
return <CreateConnection {...update} />;
}

return (
<>
<h2>Was geht?</h2>
<p>{JSON.stringify(connection)}</p>
</>
);
return <CreateIssue screenshot={screenshot} />;
};

const IssueEditor: FC<Props> = ({screenshot}) => (
<div tw="p-8 max-w-md">
<Editor screenshot={screenshot} />
</div>
);

export default IssueEditor;
3 changes: 3 additions & 0 deletions src/editor/Screenshot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export type Screenshot = {
toBlob: () => Promise<Blob>;
};
107 changes: 107 additions & 0 deletions src/editor/useCreateIssue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { useState } from "react";
import { Screenshot } from "./Screenshot";
import useConnection from "./useConnection";

export type Issue = {
subject: string;
description: string;
};

type UploadResponse = {
upload: {
token: string;
};
};

const useCreateIssue = () => {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<Error>();
const { connection } = useConnection();

const create = (issue: Issue, screenshot: Screenshot) => {
setIsLoading(true);

let baseUrl = connection.url;
if (!baseUrl.endsWith("/")) {
baseUrl += "/";
}

const filename = `bugshot-${new Date().toISOString()}.png`;
screenshot
.toBlob()
.then((blob) =>
fetch(`${baseUrl}uploads.json?filename=${filename}`, {
headers: {
"X-Redmine-API-Key": connection.apiKey,
"Content-Type": "application/octet-stream",
},
// do not prompt for basic auth if key authentication failed
credentials: "omit",
method: "POST",
body: blob,
})
)
.then((response) => {
if (!response.ok) {
throw new Error("failed to upload");
}
return response;
})
.then((response) => response.json())
.then((upload: UploadResponse) => ({
issue: {
project_id: 1,
tracker_id: 1,
status_id: 1,
priority_id: 1,
category_id: 57,
subject: issue.subject,
description: issue.description,
custom_fields: [
{
id: 1,
value: "ITZ TAM",
},
{
id: 36,
value: "Team SCM",
},
{
id: 38,
value: "Created with bugshot!",
},
],
uploads: [
{ token: upload.upload.token, filename, content_type: "image/png" },
],
},
}))
.then((body) =>
fetch(`${baseUrl}issues.json`, {
method: "POST",
headers: {
"X-Redmine-API-Key": connection.apiKey,
"Content-Type": "application/json"
},
// do not prompt for basic auth if key authentication failed
credentials: "omit",
body: JSON.stringify(body),
})
)
.then((resp) => {
if (!resp.ok) {
throw new Error("failed to create issue");
}
})
.catch(setError)
.finally(() => setIsLoading(false));
};

return {
create,
isLoading,
error,
};
};

export default useCreateIssue;

0 comments on commit 0e3bb86

Please sign in to comment.