mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-16 19:57:52 +01:00
feat: add github issue creation dialog
This commit is contained in:
@@ -34,6 +34,7 @@
|
|||||||
"nncrypto": "file:packages/nncrypto",
|
"nncrypto": "file:packages/nncrypto",
|
||||||
"nncryptoworker": "file:packages/nncryptoworker",
|
"nncryptoworker": "file:packages/nncryptoworker",
|
||||||
"notes-core": "npm:@streetwriters/notesnook-core@latest",
|
"notes-core": "npm:@streetwriters/notesnook-core@latest",
|
||||||
|
"platform": "^1.3.6",
|
||||||
"print-js": "^1.6.0",
|
"print-js": "^1.6.0",
|
||||||
"qclone": "^1.0.4",
|
"qclone": "^1.0.4",
|
||||||
"rangy": "^1.3.0",
|
"rangy": "^1.3.0",
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ async function initializeDatabase() {
|
|||||||
API_HOST: "http://192.168.10.29:5264",
|
API_HOST: "http://192.168.10.29:5264",
|
||||||
AUTH_HOST: "http://192.168.10.29:8264",
|
AUTH_HOST: "http://192.168.10.29:8264",
|
||||||
SSE_HOST: "http://192.168.10.29:7264",
|
SSE_HOST: "http://192.168.10.29:7264",
|
||||||
|
ISSUES_HOST: "http://192.168.10.29:2624",
|
||||||
});
|
});
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|||||||
@@ -563,3 +563,13 @@ export function showAnnouncementDialog(announcement, remove) {
|
|||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function showIssueDialog() {
|
||||||
|
return showDialog((Dialogs, perform) => (
|
||||||
|
<Dialogs.IssueDialog
|
||||||
|
onClose={(res) => {
|
||||||
|
perform(res);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|||||||
@@ -137,7 +137,6 @@ function Dialog(props) {
|
|||||||
opacity: props.positiveButton.disabled ? 0.7 : 1,
|
opacity: props.positiveButton.disabled ? 0.7 : 1,
|
||||||
":hover": { bg: "bgSecondary" },
|
":hover": { bg: "bgSecondary" },
|
||||||
}}
|
}}
|
||||||
autoFocus
|
|
||||||
disabled={props.positiveButton.disabled || false}
|
disabled={props.positiveButton.disabled || false}
|
||||||
onClick={
|
onClick={
|
||||||
!props.positiveButton.disabled
|
!props.positiveButton.disabled
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import FeatureDialog from "./feature-dialog";
|
|||||||
import TrackingDetailsDialog from "./trackingdetailsdialog";
|
import TrackingDetailsDialog from "./trackingdetailsdialog";
|
||||||
import ReminderDialog from "./reminderdialog";
|
import ReminderDialog from "./reminderdialog";
|
||||||
import AnnouncementDialog from "./announcementdialog";
|
import AnnouncementDialog from "./announcementdialog";
|
||||||
|
import IssueDialog from "./issuedialog";
|
||||||
|
|
||||||
const Dialogs = {
|
const Dialogs = {
|
||||||
AddNotebookDialog,
|
AddNotebookDialog,
|
||||||
@@ -32,5 +33,6 @@ const Dialogs = {
|
|||||||
ProgressDialog,
|
ProgressDialog,
|
||||||
ReminderDialog,
|
ReminderDialog,
|
||||||
AnnouncementDialog,
|
AnnouncementDialog,
|
||||||
|
IssueDialog,
|
||||||
};
|
};
|
||||||
export default Dialogs;
|
export default Dialogs;
|
||||||
|
|||||||
139
apps/web/src/components/dialogs/issuedialog.js
Normal file
139
apps/web/src/components/dialogs/issuedialog.js
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
import { Flex, Text } from "rebass";
|
||||||
|
import { getAppVersion } from "../../utils/useVersion";
|
||||||
|
import Field from "../field";
|
||||||
|
import Dialog from "./dialog";
|
||||||
|
import platform from "platform";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { db } from "../../common/db";
|
||||||
|
import { confirm } from "../../common/dialog-controller";
|
||||||
|
|
||||||
|
const PLACEHOLDERS = {
|
||||||
|
title: "Briefly describe what happened",
|
||||||
|
body: `Tell us more about the issue you are facing.
|
||||||
|
|
||||||
|
For example things like:
|
||||||
|
1. Steps to reproduce the issue
|
||||||
|
2. Things you have tried so far
|
||||||
|
3. etc.
|
||||||
|
|
||||||
|
This is all optional, of course.`,
|
||||||
|
};
|
||||||
|
|
||||||
|
function getDeviceInfo() {
|
||||||
|
const appVersion = getAppVersion().formatted;
|
||||||
|
const os = platform.os;
|
||||||
|
const browser = `${platform.name} ${platform.version}`;
|
||||||
|
|
||||||
|
return `App version: ${appVersion}
|
||||||
|
OS: ${os}
|
||||||
|
Browser: ${browser}`;
|
||||||
|
}
|
||||||
|
const BODY_TEMPLATE = (body) => {
|
||||||
|
const info = `**Device information:**\n${getDeviceInfo()}`;
|
||||||
|
if (!body) return info;
|
||||||
|
return `${body}\n\n${info}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
function IssueDialog(props) {
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
const [error, setError] = useState();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
isOpen={true}
|
||||||
|
title={"Report an issue"}
|
||||||
|
scrollable
|
||||||
|
description={
|
||||||
|
"Let us know if you are facing an issue. We'll do our best to sort it out for you."
|
||||||
|
}
|
||||||
|
onClose={props.onClose}
|
||||||
|
positiveButton={{
|
||||||
|
text: "Report",
|
||||||
|
props: {
|
||||||
|
form: "issueForm",
|
||||||
|
},
|
||||||
|
loading: isSubmitting,
|
||||||
|
disabled: isSubmitting,
|
||||||
|
}}
|
||||||
|
negativeButton={{ text: "Cancel", onClick: props.onClose }}
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
id="issueForm"
|
||||||
|
as="form"
|
||||||
|
flexDirection="column"
|
||||||
|
onSubmit={async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
try {
|
||||||
|
setIsSubmitting(true);
|
||||||
|
setError();
|
||||||
|
|
||||||
|
const formData = new FormData(e.target);
|
||||||
|
const requestData = Object.fromEntries(formData.entries());
|
||||||
|
if (!requestData.title.trim() || !requestData.body.trim()) return;
|
||||||
|
requestData.body = BODY_TEMPLATE(requestData.body);
|
||||||
|
const url = await db.debug.report(
|
||||||
|
requestData.title,
|
||||||
|
requestData.body
|
||||||
|
);
|
||||||
|
confirm({
|
||||||
|
title: "Thank you for reporting!",
|
||||||
|
message: (
|
||||||
|
<>
|
||||||
|
You can track your issue at{" "}
|
||||||
|
<Text as="a" href={url} sx={{ lineBreak: "anywhere" }}>
|
||||||
|
{url}
|
||||||
|
</Text>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
setError(e.message);
|
||||||
|
} finally {
|
||||||
|
setIsSubmitting(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Field
|
||||||
|
required
|
||||||
|
label="Title"
|
||||||
|
id="title"
|
||||||
|
name="title"
|
||||||
|
placeholder={PLACEHOLDERS.title}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
<Field
|
||||||
|
as="textarea"
|
||||||
|
required
|
||||||
|
variant="forms.input"
|
||||||
|
label="Description"
|
||||||
|
id="body"
|
||||||
|
name="body"
|
||||||
|
placeholder={PLACEHOLDERS.body}
|
||||||
|
sx={{ mt: 1 }}
|
||||||
|
styles={{
|
||||||
|
input: {
|
||||||
|
minHeight: 150,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Text variant="subBody" mt={1}>
|
||||||
|
{getDeviceInfo()
|
||||||
|
.split("\n")
|
||||||
|
.map((t) => (
|
||||||
|
<>
|
||||||
|
{t}
|
||||||
|
<br />
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</Text>
|
||||||
|
{error && (
|
||||||
|
<Text bg="errorBg" variant="error" mt={1} px={1}>
|
||||||
|
Error: {error}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IssueDialog;
|
||||||
@@ -49,6 +49,7 @@ function Field(props) {
|
|||||||
validatePassword,
|
validatePassword,
|
||||||
onError,
|
onError,
|
||||||
variant = "input",
|
variant = "input",
|
||||||
|
as = "input",
|
||||||
} = props;
|
} = props;
|
||||||
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
|
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
|
||||||
const [rules, setRules] = useState(passwordValidationRules);
|
const [rules, setRules] = useState(passwordValidationRules);
|
||||||
@@ -83,6 +84,7 @@ function Field(props) {
|
|||||||
|
|
||||||
<Flex mt={1} sx={{ position: "relative" }}>
|
<Flex mt={1} sx={{ position: "relative" }}>
|
||||||
<Input
|
<Input
|
||||||
|
as={as}
|
||||||
data-test-id={props["data-test-id"]}
|
data-test-id={props["data-test-id"]}
|
||||||
variant={variant}
|
variant={variant}
|
||||||
defaultValue={defaultValue}
|
defaultValue={defaultValue}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from "react";
|
import { useState } from "react";
|
||||||
import MDIIcon from "@mdi/react";
|
import MDIIcon from "@mdi/react";
|
||||||
import {
|
import {
|
||||||
mdiPlus,
|
mdiPlus,
|
||||||
@@ -111,11 +111,9 @@ import {
|
|||||||
mdiDatabaseLockOutline,
|
mdiDatabaseLockOutline,
|
||||||
mdiPaletteSwatchOutline,
|
mdiPaletteSwatchOutline,
|
||||||
mdiAppleIos,
|
mdiAppleIos,
|
||||||
mdiCellphoneAndroid,
|
|
||||||
mdiMicrosoftWindows,
|
mdiMicrosoftWindows,
|
||||||
mdiLinux,
|
mdiLinux,
|
||||||
mdiApple,
|
mdiApple,
|
||||||
mdiWeb,
|
|
||||||
mdiYoutube,
|
mdiYoutube,
|
||||||
mdiCheckCircleOutline,
|
mdiCheckCircleOutline,
|
||||||
mdiAndroid,
|
mdiAndroid,
|
||||||
@@ -124,6 +122,7 @@ import {
|
|||||||
mdiGoogleChrome,
|
mdiGoogleChrome,
|
||||||
mdiFirefox,
|
mdiFirefox,
|
||||||
mdiAppleSafari,
|
mdiAppleSafari,
|
||||||
|
mdiBugOutline,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import { useTheme } from "emotion-theming";
|
import { useTheme } from "emotion-theming";
|
||||||
import { AnimatedFlex } from "../animated";
|
import { AnimatedFlex } from "../animated";
|
||||||
@@ -312,3 +311,4 @@ export const Embed = createIcon(mdiYoutube);
|
|||||||
export const Anonymous = createIcon(mdiIncognito);
|
export const Anonymous = createIcon(mdiIncognito);
|
||||||
export const CloudLock = createIcon(mdiCloudLockOutline);
|
export const CloudLock = createIcon(mdiCloudLockOutline);
|
||||||
export const Timebomb = createIcon(mdiBomb);
|
export const Timebomb = createIcon(mdiBomb);
|
||||||
|
export const Issue = createIcon(mdiBugOutline);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
SyncError,
|
SyncError,
|
||||||
Checkmark,
|
Checkmark,
|
||||||
Alert,
|
Alert,
|
||||||
|
Issue,
|
||||||
} from "../icons";
|
} from "../icons";
|
||||||
import { useStore as useUserStore } from "../../stores/user-store";
|
import { useStore as useUserStore } from "../../stores/user-store";
|
||||||
import { useStore as useAppStore } from "../../stores/app-store";
|
import { useStore as useAppStore } from "../../stores/app-store";
|
||||||
@@ -17,6 +18,7 @@ import useAutoUpdater from "../../hooks/use-auto-updater";
|
|||||||
import downloadUpdate from "../../commands/download-update";
|
import downloadUpdate from "../../commands/download-update";
|
||||||
import installUpdate from "../../commands/install-update";
|
import installUpdate from "../../commands/install-update";
|
||||||
import checkForUpdate from "../../commands/check-for-update";
|
import checkForUpdate from "../../commands/check-for-update";
|
||||||
|
import { showIssueDialog } from "../../common/dialog-controller";
|
||||||
|
|
||||||
function StatusBar() {
|
function StatusBar() {
|
||||||
const user = useUserStore((state) => state.user);
|
const user = useUserStore((state) => state.user);
|
||||||
@@ -80,6 +82,18 @@ function StatusBar() {
|
|||||||
</Text>
|
</Text>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
<Button
|
||||||
|
variant="statusitem"
|
||||||
|
display="flex"
|
||||||
|
onClick={() => showIssueDialog()}
|
||||||
|
sx={{ alignItems: "center", justifyContent: "center" }}
|
||||||
|
title="Facing an issue? Click here to create a bug report."
|
||||||
|
>
|
||||||
|
<Issue size={12} />
|
||||||
|
<Text variant="subBody" color="bgSecondaryText" ml={1}>
|
||||||
|
Report an issue
|
||||||
|
</Text>
|
||||||
|
</Button>
|
||||||
{processingStatuses?.map(({ key, status, progress }) => (
|
{processingStatuses?.map(({ key, status, progress }) => (
|
||||||
<Flex key={key} ml={1} alignItems="center" justifyContent="center">
|
<Flex key={key} ml={1} alignItems="center" justifyContent="center">
|
||||||
<Loading size={12} />
|
<Loading size={12} />
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import AccentItem from "../components/accent-item";
|
|||||||
import accents from "../theme/accents";
|
import accents from "../theme/accents";
|
||||||
import {
|
import {
|
||||||
showEmailVerificationDialog,
|
showEmailVerificationDialog,
|
||||||
|
showIssueDialog,
|
||||||
showTrackingDetailsDialog,
|
showTrackingDetailsDialog,
|
||||||
} from "../common/dialog-controller";
|
} from "../common/dialog-controller";
|
||||||
import { showLogoutConfirmation } from "../common/dialog-controller";
|
import { showLogoutConfirmation } from "../common/dialog-controller";
|
||||||
@@ -96,8 +97,8 @@ const otherItems = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Report an issue",
|
title: "Report an issue",
|
||||||
description: "Facing an issue? Report it on our Github Repo.",
|
description: "Facing an issue? Click here to create a bug report.",
|
||||||
link: "https://github.com/streetwriters/notesnook/issues/new",
|
onClick: () => showIssueDialog(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Join our Discord community",
|
title: "Join our Discord community",
|
||||||
@@ -629,7 +630,8 @@ function Settings(props) {
|
|||||||
key={item.title}
|
key={item.title}
|
||||||
variant="list"
|
variant="list"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
window.open(item.link, "_blank");
|
if (item.onClick) item.onClick();
|
||||||
|
else if (item.link) window.open(item.link, "_blank");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Tip text={item.title} tip={item.description} />
|
<Tip text={item.title} tip={item.description} />
|
||||||
|
|||||||
Reference in New Issue
Block a user