Skip to content
Permalink

Comparing changes

This is a direct comparison between two commits made in this repository or its related repositories. View the default comparison for this range or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: bitcointranscripts/transcription-review-front-end
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 404b6c5567dcea1b7a9d5d8f670b228659b3bcdd
Choose a base ref
..
head repository: bitcointranscripts/transcription-review-front-end
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 057997038eec8a2865449d4c29c195c58091d941
Choose a head ref
47 changes: 24 additions & 23 deletions src/components/modals/SuggestModal.tsx
Original file line number Diff line number Diff line change
@@ -15,7 +15,6 @@ import {
ModalHeader,
ModalOverlay,
Text,
useToast,
} from "@chakra-ui/react";
import { type FormEvent, useState, ChangeEvent } from "react";

@@ -35,7 +34,7 @@ const defaultFormValues = {
} satisfies FormValues;

const SuggestModal = ({ handleClose, isOpen }: SuggestModalProps) => {
const { suggestSource } = useGithub()
const { suggestSource } = useGithub();
const [urlError, setUrlError] = useState("");
const [formValues, setFormValues] = useState<FormValues>(defaultFormValues);
const { data: selectableListData } = useGetMetadata();
@@ -47,13 +46,12 @@ const SuggestModal = ({ handleClose, isOpen }: SuggestModalProps) => {
};

const handleUrlChange = (e: ChangeEvent<HTMLInputElement>) => {
setFormValues((v) => ({ ...v, url: e.target.value }))
setFormValues((v) => ({ ...v, url: e.target.value }));
setUrlError("");
}
};

const validateUrl = (urlString: string): boolean => {
try {

const url = new URL(urlString.trim());

const urlExists = selectableListData?.media.some((mediaUrl) =>
@@ -81,15 +79,18 @@ const SuggestModal = ({ handleClose, isOpen }: SuggestModalProps) => {
const isUrlValid = validateUrl(formValues.url);
if (!isUrlValid) return;

await suggestSource.mutateAsync({
title: formValues.title,
media: formValues.url,
targetRepository: getPRRepo()
}, {
onSuccess: () => {
resetAndCloseForm()
await suggestSource.mutateAsync(
{
title: formValues.title,
media: formValues.url,
targetRepository: getPRRepo(),
},
{
onSuccess: () => {
resetAndCloseForm();
},
}
})
);
};

const formIsComplete = !!(formValues.title.trim() && formValues.url.trim());
@@ -117,9 +118,8 @@ const SuggestModal = ({ handleClose, isOpen }: SuggestModalProps) => {
fontSize={{ base: "xs", lg: "sm" }}
textAlign="center"
>
We manually review every suggestion to ensure it meets our
standards for reliable,
technical Bitcoin content.
We manually review every suggestion to ensure it meets our standards
for reliable, technical Bitcoin content.
</Text>
</ModalHeader>
<form onSubmit={handleSubmit}>
@@ -135,7 +135,11 @@ const SuggestModal = ({ handleClose, isOpen }: SuggestModalProps) => {
required
/>
</FormControl>
<FormControl isRequired gap={{ base: "6px", lg: "xs" }} isInvalid={!!urlError}>
<FormControl
isRequired
gap={{ base: "6px", lg: "xs" }}
isInvalid={!!urlError}
>
<FormLabel>Source&apos;s URL</FormLabel>
<Input
type="url"
@@ -156,10 +160,7 @@ const SuggestModal = ({ handleClose, isOpen }: SuggestModalProps) => {
</FormControl>
</Flex>
</ModalBody>
<ModalFooter
gap={{ base: "8px", lg: "md" }}
w="full"
>
<ModalFooter gap={{ base: "8px", lg: "md" }} w="full">
<Button
w="full"
mx="auto"
@@ -185,6 +186,6 @@ const SuggestModal = ({ handleClose, isOpen }: SuggestModalProps) => {
</ModalContent>
</Modal>
);
}
};

export default SuggestModal
export default SuggestModal;
10 changes: 5 additions & 5 deletions src/components/modals/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export { default as ReviewGuidelinesModal } from "./ReviewGuidelinesModal"
export { default as SubmitTranscriptModal } from "./SubmitTranscriptModal"
export { default as RestoreOriginalModal } from "./RestoreOriginalModal"
export { default as SuggestModal } from "./SuggestModal"
export { default as SelectSpeakerModal } from "./SelectSpeakerModal"
export { default as ReviewGuidelinesModal } from "./ReviewGuidelinesModal";
export { default as SubmitTranscriptModal } from "./SubmitTranscriptModal";
export { default as RestoreOriginalModal } from "./RestoreOriginalModal";
export { default as SuggestModal } from "./SuggestModal";
export { default as SelectSpeakerModal } from "./SelectSpeakerModal";
2 changes: 1 addition & 1 deletion src/components/sideBarContentEdit/selectbox.tsx
Original file line number Diff line number Diff line change
@@ -46,7 +46,7 @@ export const SelectBox = ({
addItem,
autoCompleteList,
handleAutoCompleteSelect,
selectedValue
selectedValue,
}: SelectBoxProps) => {
const inputRef = useRef<HTMLInputElement>(null);
const { onClose, onOpen, isOpen } = useDisclosure();
21 changes: 12 additions & 9 deletions src/components/transcript/components/StatusLabel.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { MdOutlineAccessTimeFilled, MdCheckCircleOutline } from "react-icons/md"
import { Box } from "@chakra-ui/react"
import {
MdOutlineAccessTimeFilled,
MdCheckCircleOutline,
} from "react-icons/md";
import { Box } from "@chakra-ui/react";

import { getTimeLeftText } from "@/utils"
import { Review } from "../../../../types"
import { getTimeLeftText } from "@/utils";
import { Review } from "../../../../types";

type Props = Pick<Review, 'createdAt' | 'submittedAt'>
type Props = Pick<Review, "createdAt" | "submittedAt">;

const formatTimeDifference = (date: Date) => {
const diffMs = Date.now() - new Date(date).getTime();
@@ -19,7 +22,7 @@ const formatTimeDifference = (date: Date) => {
} else {
return `Submitted ${Math.floor(diffHours)} hours ago`;
}
}
};

const StatusLabel = ({ createdAt, submittedAt }: Props) => {
const textColor = submittedAt ? "green.700" : "red.700";
@@ -50,7 +53,7 @@ const StatusLabel = ({ createdAt, submittedAt }: Props) => {
</>
)}
</Box>
)
}
);
};

export default StatusLabel
export default StatusLabel;
2 changes: 1 addition & 1 deletion src/components/transcript/components/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { default as StatusLabel } from "./StatusLabel"
export { default as StatusLabel } from "./StatusLabel";
4 changes: 2 additions & 2 deletions src/pages/api/github/fork.ts
Original file line number Diff line number Diff line change
@@ -22,8 +22,8 @@ export default async function handler(
// Fork the repository
const result = await octokit.request("POST /repos/{owner}/{repo}/forks", {
owner,
repo
})
repo,
});
res.status(200).json(result.data);
} catch (error) {
console.error("fork failed");
24 changes: 8 additions & 16 deletions src/pages/api/github/newBranch.ts
Original file line number Diff line number Diff line change
@@ -27,17 +27,15 @@ export async function createNewBranch({

// Get baseBranch sha
const baseRefSha = await octokit
.request("GET /repos/{owner}/{repo}/git/ref/{ref}",
{
owner: repositoryOwner,
repo: upstreamRepo,
ref: `heads/${baseBranch}`,
}
)
.request("GET /repos/{owner}/{repo}/git/ref/{ref}", {
owner: repositoryOwner,
repo: upstreamRepo,
ref: `heads/${baseBranch}`,
})
.then((result) => result.data.object.sha)
.catch((err: any) => {
throw new Error(err?.message ?? "Cannot find base branch");
})
});

// Create new branch
return await octokit
@@ -48,9 +46,7 @@ export async function createNewBranch({
sha: baseRefSha,
})
.catch((err) => {
throw new Error(
err?.message ?? "Error creating new branch"
);
throw new Error(err?.message ?? "Error creating new branch");
});
}

@@ -64,11 +60,7 @@ export default async function handler(
return res.status(401).json({ message: "Unauthorized" });
}

const {
upstreamRepo,
baseBranch,
branchName
} = req.body;
const { upstreamRepo, baseBranch, branchName } = req.body;

// Initialize Octokit with the user's access token
const octokit = new Octokit({ auth: session.accessToken });
13 changes: 3 additions & 10 deletions src/pages/api/github/pr.ts
Original file line number Diff line number Diff line change
@@ -17,14 +17,7 @@ export default async function handler(
// Initialize Octokit with the user's access token
const octokit = new Octokit({ auth: session.accessToken });

const {
owner,
repo,
title,
body,
head,
base
} = req.body;
const { owner, repo, title, body, head, base } = req.body;

try {
const prResult = await createPullRequest({
@@ -35,11 +28,11 @@ export default async function handler(
body,
head,
base,
})
});

return res.status(200).json(prResult.data);
} catch (error: any) {
console.error(error)
console.error(error);
res.status(500).json({
message:
error?.message ?? "Error occurred while creating the Pull Request",
54 changes: 28 additions & 26 deletions src/pages/api/github/read.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,38 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import axios from 'axios';
import type { NextApiRequest, NextApiResponse } from "next";
import axios from "axios";

import { auth } from "../auth/[...nextauth]";

async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'POST') {
return res.status(405).json({ message: 'Method Not Allowed' });
}
if (req.method !== "POST") {
return res.status(405).json({ message: "Method Not Allowed" });
}

// Authenticate the session and retrieve the access token
const session = await auth(req, res);
if (!session || !session.accessToken || !session.user?.githubUsername) {
return res.status(401).json({ message: "Unauthorized" });
}
// Authenticate the session and retrieve the access token
const session = await auth(req, res);
if (!session || !session.accessToken || !session.user?.githubUsername) {
return res.status(401).json({ message: "Unauthorized" });
}

const { branchUrl } = req.body;
if (typeof branchUrl !== 'string') {
return res.status(400).json({ message: 'Invalid request body' });
}
const { branchUrl } = req.body;
if (typeof branchUrl !== "string") {
return res.status(400).json({ message: "Invalid request body" });
}

try {
const response = await axios.get(branchUrl, {
headers: {
'Accept': 'application/vnd.github.v3.raw',
// Use the GitHub access token for authenticated GitHub requests
'Authorization': `token ${session.accessToken}`
}
});
res.status(200).json({ content: response.data });
} catch (error: any) {
res.status(error.response.status).json({ message: 'Failed to fetch GitHub file content' });
}
try {
const response = await axios.get(branchUrl, {
headers: {
Accept: "application/vnd.github.v3.raw",
// Use the GitHub access token for authenticated GitHub requests
Authorization: `token ${session.accessToken}`,
},
});
res.status(200).json({ content: response.data });
} catch (error: any) {
res
.status(error.response.status)
.json({ message: "Failed to fetch GitHub file content" });
}
}

export default handler;
16 changes: 5 additions & 11 deletions src/pages/api/github/save.ts
Original file line number Diff line number Diff line change
@@ -22,22 +22,16 @@ export default async function handler(
// Initialize Octokit with the user's access token
const octokit = new Octokit({ auth: session.accessToken });

const {
repo,
filePath,
fileContent,
branch,
} = req.body;
const { repo, filePath, fileContent, branch } = req.body;

try {

const fileSha = await getFileSha({
octokit,
owner: session.user.githubUsername,
repo,
path: filePath,
branch
})
branch,
});

await updateOrCreateFile({
octokit,
@@ -46,8 +40,8 @@ export default async function handler(
path: filePath,
fileContent,
branch,
sha: fileSha
})
sha: fileSha,
});

res.status(200).json({ message: "Successfully saved edits" });
} catch (error: any) {
2 changes: 1 addition & 1 deletion src/services/api/github/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * from "./useGithub"
export * from "./useGithub";
541 changes: 281 additions & 260 deletions src/services/api/github/useGithub.ts

Large diffs are not rendered by default.

64 changes: 36 additions & 28 deletions src/utils/github.ts
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@ interface GitHubFileParams extends GitHubRepoParams {
}

// Interface for getting a file SHA
interface GetFileShaParams extends GitHubFileParams { }
interface GetFileShaParams extends GitHubFileParams {}

// Interface for updating or creating a file with optional SHA
interface UpdateOrCreateFileParams extends GitHubFileParams {
@@ -137,19 +137,24 @@ export async function ensureIndexMdExists(
/**
* Fetches the SHA of a file in a given GitHub repository using named parameters.
*/
export async function getFileSha(params: GetFileShaParams): Promise<string | null> {
export async function getFileSha(
params: GetFileShaParams
): Promise<string | null> {
const { octokit, owner, repo, path, branch } = params;
try {
const response = await octokit.request("GET /repos/{owner}/{repo}/contents/{path}", {
owner,
repo,
path,
ref: branch,
});
const response = await octokit.request(
"GET /repos/{owner}/{repo}/contents/{path}",
{
owner,
repo,
path,
ref: branch,
}
);
const data = response.data as { sha: string };
return data.sha;
} catch (err: any) {
return null
return null;
throw new Error(`Cannot find ${path} SHA. Error: ${err.message}`);
}
}
@@ -159,25 +164,27 @@ export async function getFileSha(params: GetFileShaParams): Promise<string | nul
*/
export async function updateOrCreateFile(params: UpdateOrCreateFileParams) {
const { octokit, owner, repo, path, fileContent, branch, sha } = params;
const timestamp = new Date().toISOString()
const timestamp = new Date().toISOString();
try {
const response = await octokit.request("PUT /repos/:owner/:repo/contents/:path", {
owner,
repo,
path,
message: `Save Edits (${timestamp})`,
content: Buffer.from(fileContent).toString("base64"),
branch,
sha,
});
const response = await octokit.request(
"PUT /repos/:owner/:repo/contents/:path",
{
owner,
repo,
path,
message: `Save Edits (${timestamp})`,
content: Buffer.from(fileContent).toString("base64"),
branch,
sha,
}
);
return response;
} catch (err) {
console.error({ err });
throw new Error("Error updating or creating new file");
}
}


/**
* Creates a pull request in a specified GitHub repository using named parameters.
*/
@@ -190,11 +197,13 @@ export async function createPullRequest(params: CreatePullRequestParams) {
title,
head,
base,
body
body,
});
return response;
} catch (err: any) {
throw new Error(`Error creating Pull Request: ${err.response.data.errors[0].message}`);
throw new Error(
`Error creating Pull Request: ${err.response.data.errors[0].message}`
);
}
}

@@ -381,12 +390,11 @@ export function constructGithubBranchApiUrl({
return `https://api.github.com/repos/${owner}/bitcointranscripts/contents/${filePath}?ref=${newBranchName}`;
}

export function constructDpeUrl(
transcriptUrl: string
) {
const { srcOwner, srcBranch, srcDirPath, fileNameWithoutExtension } = resolveGHApiUrl(transcriptUrl);
export function constructDpeUrl(transcriptUrl: string) {
const { srcOwner, srcBranch, srcDirPath, fileNameWithoutExtension } =
resolveGHApiUrl(transcriptUrl);
const dpeFilePath = `${srcDirPath}/${fileNameWithoutExtension}/dpe.json`;
// hacky way to avoid for now to keep extra information about the metadata repo in the db
const metadataRepoBaseBranch = srcBranch == "master" ? "main" : srcBranch
return `https://api.github.com/repos/${srcOwner}/${upstreamMetadataRepo}/contents/${dpeFilePath}?ref=${metadataRepoBaseBranch}`
const metadataRepoBaseBranch = srcBranch == "master" ? "main" : srcBranch;
return `https://api.github.com/repos/${srcOwner}/${upstreamMetadataRepo}/contents/${dpeFilePath}?ref=${metadataRepoBaseBranch}`;
}
2 changes: 1 addition & 1 deletion types.ts
Original file line number Diff line number Diff line change
@@ -112,7 +112,7 @@ export type DigitalPaperEditFormat = {
export type SlateNode = {
children: { text: string; words: DigitalPaperEditWord[] };
speaker: string;
start: number
start: number;
startTimecode: string;
previousTimings: string;
type: string;
16 changes: 1 addition & 15 deletions yarn.lock
Original file line number Diff line number Diff line change
@@ -1460,35 +1460,21 @@
dependencies:
"@types/react" "*"

"@types/react@*":
"@types/react@*", "@types/react@^18.2.19":
version "18.3.3"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.3.tgz#9679020895318b0915d7a3ab004d92d33375c45f"
integrity sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==
dependencies:
"@types/prop-types" "*"
csstype "^3.0.2"

"@types/react@^18.2.19":
version "18.2.19"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.19.tgz#f77cb2c8307368e624d464a25b9675fa35f95a8b"
integrity sha512-e2S8wmY1ePfM517PqCG80CcE48Xs5k0pwJzuDZsfE8IZRRBfOMCF+XqnFxu6mWtyivum1MQm4aco+WIt6Coimw==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
csstype "^3.0.2"

"@types/sanitize-html@^2.9.0":
version "2.9.0"
resolved "https://registry.yarnpkg.com/@types/sanitize-html/-/sanitize-html-2.9.0.tgz#5b609f7592de22ef80a0930c39670329753dca1b"
integrity sha512-4fP/kEcKNj2u39IzrxWYuf/FnCCwwQCpif6wwY6ROUS1EPRIfWJjGkY3HIowY1EX/VbX5e86yq8AAE7UPMgATg==
dependencies:
htmlparser2 "^8.0.0"

"@types/scheduler@*":
version "0.23.0"
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.23.0.tgz#0a6655b3e2708eaabca00b7372fafd7a792a7b09"
integrity sha512-YIoDCTH3Af6XM5VuwGG/QL/CJqga1Zm3NkU3HZ4ZHK2fRMPYP1VczsTUqtsf43PH/iJNVlPHAo2oWX7BSdB2Hw==

"@typescript-eslint/parser@^5.4.2 || ^6.0.0":
version "6.3.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.3.0.tgz#359684c443f4f848db3c4f14674f544f169c8f46"