From 8d7e7cbf011f93a2a3433a8a67d4328096106680 Mon Sep 17 00:00:00 2001 From: kouloumos Date: Tue, 17 Sep 2024 19:02:05 +0300 Subject: [PATCH] feat: Suggest source for transcription enables the related button and onSubmit calls the intenal `/suggestSource` endpoint which then calls the newly introduced `/suggestSource` endpoint in the Review app which is then responsible for all the related logic. --- .env.example | 2 + package-lock.json | 91 +++++++++++ package.json | 1 + src/app/api/suggestSource/route.ts | 42 +++++ .../landing-page/SuggestionModal.tsx | 153 ++++++++++++++---- .../landing-page/WhyTranscripts.tsx | 2 +- 6 files changed, 260 insertions(+), 31 deletions(-) create mode 100644 .env.example create mode 100644 src/app/api/suggestSource/route.ts diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..0878fc9 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +NEXT_PUBLIC_VERCEL_ENV = "" # development | staging | production +REVIEW_APP_URL = "" \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index ff75363..88f366c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "@bitcoin-dev-project/bdp-ui": "^1.3.0", + "axios": "^1.7.7", "contentlayer2": "^0.4.6", "next": "14.2.4", "next-contentlayer2": "^0.4.6", @@ -1694,6 +1695,21 @@ "astring": "bin/astring" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/bail": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", @@ -1990,6 +2006,17 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/comma-separated-tokens": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", @@ -2108,6 +2135,14 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -2364,6 +2399,25 @@ "node": ">=8" } }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/foreground-child": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", @@ -2380,6 +2434,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/format": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", @@ -3725,6 +3792,25 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -4216,6 +4302,11 @@ "node": ">=12.0.0" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", diff --git a/package.json b/package.json index b71d273..f4452a2 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@bitcoin-dev-project/bdp-ui": "^1.3.0", + "axios": "^1.7.7", "contentlayer2": "^0.4.6", "next": "14.2.4", "next-contentlayer2": "^0.4.6", diff --git a/src/app/api/suggestSource/route.ts b/src/app/api/suggestSource/route.ts new file mode 100644 index 0000000..4cd9fac --- /dev/null +++ b/src/app/api/suggestSource/route.ts @@ -0,0 +1,42 @@ +import { NextResponse } from "next/server"; +import axios from "axios"; + +const REVIEW_APP_URL = process.env.REVIEW_APP_URL; + +export async function POST(request: Request) { + try { + const { title, media, targetRepository } = await request.json(); + + const response = await axios.post( + `${REVIEW_APP_URL}/api/github/suggestSource`, + { + title, + media, + targetRepository, + }, + { + headers: { + "Content-Type": "application/json", + }, + } + ); + + return NextResponse.json(response.data); + } catch (error) { + console.error("Error suggesting source:", error); + if (axios.isAxiosError(error)) { + const errorMessage = error.response?.data?.message || error.message; + return NextResponse.json( + { message: errorMessage }, + { status: error.response?.status || 500 } + ); + } else { + return NextResponse.json( + { + message: "An unexpected error occurred while suggesting the source", + }, + { status: 500 } + ); + } + } +} diff --git a/src/components/landing-page/SuggestionModal.tsx b/src/components/landing-page/SuggestionModal.tsx index 97b7b01..95f36b4 100644 --- a/src/components/landing-page/SuggestionModal.tsx +++ b/src/components/landing-page/SuggestionModal.tsx @@ -1,7 +1,37 @@ "use client"; +import axios from "axios"; import React, { type FormEvent, useState, ChangeEvent } from "react"; +interface SuggestSourceParams { + title: string; + media: string; + targetRepository: string; +} + +export async function suggestSource({ + title, + media, + targetRepository, +}: SuggestSourceParams): Promise { + try { + const response = await axios.post("/api/suggestSource", { + title, + media, + targetRepository, + }); + + return response.data.pr_url; + } catch (error) { + if (axios.isAxiosError(error)) { + throw new Error( + error.response?.data?.message || "Failed to suggest source" + ); + } + throw error; + } +} + type SuggestModalProps = { handleClose: () => void; isOpen: boolean; @@ -20,10 +50,15 @@ const defaultFormValues = { const SuggestModal = ({ handleClose, isOpen }: SuggestModalProps) => { const [urlError, setUrlError] = useState(""); const [formValues, setFormValues] = useState(defaultFormValues); + const [isLoading, setIsLoading] = useState(false); + const [prUrl, setPrUrl] = useState(null); + const [errorMessage, setErrorMessage] = useState(""); const resetAndCloseForm = () => { setFormValues(defaultFormValues); setUrlError(""); + setPrUrl(null); + setErrorMessage(""); handleClose(); }; @@ -34,31 +69,65 @@ const SuggestModal = ({ handleClose, isOpen }: SuggestModalProps) => { const handleSubmit = async (e: FormEvent) => { e.preventDefault(); + setIsLoading(true); + setErrorMessage(""); + setPrUrl(null); + + try { + const newPrUrl = await suggestSource({ + title: formValues.title, + media: formValues.url, + // This is based on the review frontend implementation + // TODO: find a way to test the workflow on a different repo + targetRepository: "btc transcript", + }); + setPrUrl(newPrUrl); + setFormValues(defaultFormValues); + } catch (error) { + setErrorMessage( + error instanceof Error + ? error.message + : "An error occurred while suggesting the source" + ); + } finally { + setIsLoading(false); + } }; const formIsComplete = !!(formValues.title.trim() && formValues.url.trim()); return ( -
+
{isOpen ? ( -
-
-

Suggest a Source for Transcription

-

- We manually review every suggestion to ensure it meets our standards for reliable, technical Bitcoin content. +

+
+

+ Suggest a Source for Transcription +

+

+ We manually review every suggestion to ensure it meets our + standards for reliable, technical Bitcoin content.

-
-
-

Title

+
+
+

+ Title +

setFormValues((v) => ({ @@ -67,40 +136,64 @@ const SuggestModal = ({ handleClose, isOpen }: SuggestModalProps) => { })) } required - className='px-2 py-2 lg:py-[10px] border border-gray-custom-300 rounded-[10px] focus:border-custom-accent focus:outline-none w-full min-h-[48px]' + className="px-2 py-2 lg:py-[10px] border border-gray-custom-300 rounded-[10px] focus:border-custom-accent focus:outline-none w-full min-h-[48px]" />
-
-

Source's URL

+
+

+ Source's URL +

{ - setFormValues((v) => ({ ...v, url: e.target.value })); - setUrlError(""); - }} + onChange={handleUrlChange} required maxLength={255} - className='px-2 py-2 lg:py-[10px] border border-gray-custom-300 rounded-[10px] focus:border-custom-accent focus:outline-none w-full min-h-[48px]' + className="px-2 py-2 lg:py-[10px] border border-gray-custom-300 rounded-[10px] focus:border-custom-accent focus:outline-none w-full min-h-[48px]" /> -

- {urlError ? urlError : "Please enter the full URL, including http:// or https://"} +

+ {urlError + ? urlError + : "Please enter the full URL, including http:// or https://"}

-
+ {prUrl && ( +

+ Source suggested successfully! PR created at:{" "} + + {prUrl} + +

+ )} + {errorMessage && ( +

+ {errorMessage} +

+ )} +
diff --git a/src/components/landing-page/WhyTranscripts.tsx b/src/components/landing-page/WhyTranscripts.tsx index 57dfd72..12769c2 100644 --- a/src/components/landing-page/WhyTranscripts.tsx +++ b/src/components/landing-page/WhyTranscripts.tsx @@ -92,7 +92,7 @@ const WhyTranscripts = () => { Review Transcripts, Earn Sats