diff --git a/frontend/src/components/Pages/Home/index.js b/frontend/src/components/Pages/Home/index.js
index 890a0aa..0b198ac 100644
--- a/frontend/src/components/Pages/Home/index.js
+++ b/frontend/src/components/Pages/Home/index.js
@@ -5,6 +5,7 @@ import { themes, animations, layouts, fonts, colorValues, quoteTypes } from '../
import TextField from '@material-ui/core/TextField';
import Autocomplete from '@material-ui/lab/Autocomplete';
import ContributorsCard from '../../ContributorsCard/ContributorCard'
+import useQuoteAuthors from '../../../util/authors';
import { makeStyles } from '@material-ui/core/styles';
const useStyles = makeStyles({
@@ -24,6 +25,9 @@ const Home = () => {
const [bgColor, setBgColor] = useState(null);
const [borderColor, setBorderColor] = useState(null);
const [quoteType, setQuoteType] = useState("random");
+ const [quoteAuthor, setQuoteAuthor] = useState(null);
+
+ const { quoteAuthors, loadingQuoteAuthors } = useQuoteAuthors();
const classes = useStyles();
@@ -158,11 +162,25 @@ const Home = () => {
/>
+
+ {
+ setQuoteAuthor(newValue)
+ }}
+ renderInput={(params) => }
+ />
+
+
-
+
Other layouts
@@ -171,7 +189,7 @@ const Home = () => {
layouts.filter((item) => item !== layout).map((restLayout) => {
return (
-
+
)
})
diff --git a/frontend/src/components/organisms/TemplateCard/index.js b/frontend/src/components/organisms/TemplateCard/index.js
index 6e2fe0a..12c5171 100644
--- a/frontend/src/components/organisms/TemplateCard/index.js
+++ b/frontend/src/components/organisms/TemplateCard/index.js
@@ -21,11 +21,12 @@ const TemplateCard = (props) => {
const [snackbarMessage, setSnackbarMessage] = useState("");
const [isImageLoaded, setImageLoaded] = useState(false);
const originUrl = serverUrl; // Note: PORT 3004 since in server is served via that port. Frontend independently served on port 3000
+ const author = "Open Source";
const template = new Template();
const data = {
quote: "This is going to be the Github quote for your README",
- author: "Open Source",
+ author: props.quoteAuthor ?? author,
};
const theme = { ...mainThemes[props.theme] };
@@ -78,6 +79,7 @@ const TemplateCard = (props) => {
...(props.bgColor && { bgColor: props.bgColor }),
...(props.fontColor && { fontColor: props.fontColor }),
...(isLayoutDefault && props.borderColor && { borderColor }),
+ ...(props.quoteAuthor && { author: props.quoteAuthor }),
});
const quoteUrl = `${originUrl}/quote?${params.toString()}`;
diff --git a/frontend/src/util/authors/index.js b/frontend/src/util/authors/index.js
new file mode 100644
index 0000000..23e472f
--- /dev/null
+++ b/frontend/src/util/authors/index.js
@@ -0,0 +1,39 @@
+import { useState, useCallback, useEffect } from "react";
+
+import { serverUrl } from "../../components/Constants/urlConfig";
+
+const useQuoteAuthors = () => {
+ const originUrl = serverUrl;
+ const [quoteAuthors, setQuoteAuthors] = useState([]);
+ const [loadingQuoteAuthors, setLoadingQuoteAuthors] = useState(false);
+
+ const fetchQuoteAuthors = useCallback(async () => {
+ setLoadingQuoteAuthors(true);
+ try {
+ const response = await fetch(`${originUrl}/authors`);
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const data = await response.json();
+ if (data) {
+ setQuoteAuthors(data);
+ }
+ } catch (error) {
+ console.error("Failed to fetch quote authors:", error);
+ } finally {
+ setLoadingQuoteAuthors(false);
+ }
+ }, [originUrl]);
+
+ useEffect(() => {
+ fetchQuoteAuthors();
+ }, [fetchQuoteAuthors]);
+
+ return {
+ quoteAuthors,
+ loadingQuoteAuthors
+ };
+}
+
+export default useQuoteAuthors;
\ No newline at end of file
diff --git a/src/api/controllers/authorsController.js b/src/api/controllers/authorsController.js
new file mode 100644
index 0000000..0798851
--- /dev/null
+++ b/src/api/controllers/authorsController.js
@@ -0,0 +1,39 @@
+const quoteService = require("../services/quotesService");
+
+const authorsController = async (req, res, next) => {
+
+ try {
+
+ let quotesUrl = req.query.quotesUrl || '';
+
+ let quoteCategory = req.query.quoteCategory || '';
+
+ let quoteObject = {
+ quotesUrl,
+ quoteCategory,
+ }
+
+ const authors = await quoteService.getAuthors(quoteObject);
+
+ res.setHeader("Content-Type", "application/json");
+
+ res.header(
+ "Cache-Control",
+ "no-cache,max-age=0,no-store,s-maxage=0,proxy-revalidate"
+ );
+ res.header("Pragma", "no-cache");
+ res.header("Expires", "-1");
+ res.json(authors);
+
+ } catch (error) {
+ console.error(error);
+ res.send({
+ name: error.name,
+ message: error.message,
+ });
+ }
+};
+
+module.exports = {
+ authorsController
+};
diff --git a/src/api/controllers/quotesController.js b/src/api/controllers/quotesController.js
index d096e0b..3561e26 100644
--- a/src/api/controllers/quotesController.js
+++ b/src/api/controllers/quotesController.js
@@ -34,6 +34,8 @@ const quoteController = async (req, res, next) => {
let quoteType = req.query.quoteType || '';
+ let author = req.query.author || undefined;
+
let quoteObject = {
theme,
animation,
@@ -42,6 +44,7 @@ const quoteController = async (req, res, next) => {
quoteCategory,
font,
quoteType,
+ author,
borderColor
}
diff --git a/src/api/routes/quotes-router.js b/src/api/routes/quotes-router.js
index 22d6770..b0a35eb 100644
--- a/src/api/routes/quotes-router.js
+++ b/src/api/routes/quotes-router.js
@@ -116,12 +116,16 @@
const controllers = require('../controllers/quotesController');
+const authorsController = require('../controllers/authorsController');
const defineRoutes = (app) => {
// get a quote
app.get('/quote', controllers.quoteController);
+ // get authors
+ app.get('/authors', authorsController.authorsController);
+
}
module.exports = defineRoutes;
diff --git a/src/api/services/quotesService.js b/src/api/services/quotesService.js
index 7075555..b27491a 100644
--- a/src/api/services/quotesService.js
+++ b/src/api/services/quotesService.js
@@ -12,10 +12,16 @@ getQuoteIndex = (apiResponseLength, quoteType) => {
return (quoteType === "quote-for-the-day" ? epoch % apiResponseLength : Math.random() * apiResponseLength);
}
+const filterQuotesByAuthor = (quotes, author) => {
+ const quotesFiltered = quotes.filter((quote) => quote?.author?.toString().toLowerCase().trim() === author.toString().toLowerCase().trim());
+
+ return quotesFiltered.length > 0 ? quotesFiltered : quotes;
+}
+
const getQuote = async (quoteObj) => {
try {
- let { theme, animation, layout, quotesUrl, quoteCategory, font, quoteType, borderColor } = quoteObj;
+ let { theme, animation, layout, quotesUrl, quoteCategory, font, quoteType, borderColor, author } = quoteObj;
let apiResponse;
let { customQuotesUrl, isValidUrl } = await getValidUrl(quotesUrl);
let isCustomQuote = false;
@@ -24,29 +30,43 @@ const getQuote = async (quoteObj) => {
//url from params is valid, proceed to verfiy the data
apiResponse = await requestApi(customQuotesUrl);
+ if (author) {
+ apiResponse = filterQuotesByAuthor(apiResponse, author);
+ }
+
if (apiResponse.length > 0) {
apiResponse = apiResponse[Math.floor(getQuoteIndex(apiResponse.length, quoteType))];
- if (!apiResponse.quote && !apiResponse.author) {
- apiResponse = await requestApi(url);
- } else {
+
+ if (apiResponse.quote && apiResponse.author) {
isCustomQuote = true;
}
- } else {
- apiResponse = await requestApi(url);
}
}
else if (quoteCategory) {
apiResponse = quoteFromCategory[quoteCategory];
+
+ if (author) {
+ apiResponse = filterQuotesByAuthor(apiResponse, author);
+ }
+
apiResponse = apiResponse[Math.floor(getQuoteIndex(apiResponse.length, quoteType))];
+
isCustomQuote = true;
}
- else {
+
+ if(!isCustomQuote) {
apiResponse = await requestApi(url);
+
+ if (author) {
+ apiResponse = filterQuotesByAuthor(apiResponse, author);
+ }
+
+ apiResponse = apiResponse[Math.floor(getQuoteIndex(apiResponse.length, quoteType))];
}
const template = new Template();
template.setTheme(theme);
- template.setData(isCustomQuote ? apiResponse : apiResponse[Math.floor(getQuoteIndex(apiResponse.length, quoteType))]);
+ template.setData(apiResponse);
template.setFont(font);
template.setAnimation(animation);
template.setBorderColor(borderColor);
@@ -59,6 +79,46 @@ const getQuote = async (quoteObj) => {
}
};
+const getAuthors = async (quoteObj) => {
+ try {
+ let {quotesUrl, quoteCategory} = quoteObj;
+ let apiResponse;
+ let { customQuotesUrl, isValidUrl } = await getValidUrl(quotesUrl);
+ let isCustomQuote = false;
+
+ if (isValidUrl) {
+ //url from params is valid, proceed to verfiy the data
+ apiResponse = await requestApi(customQuotesUrl);
+
+ if (apiResponse.length > 0) {
+ if (apiResponse[0].author) {
+ isCustomQuote = true;
+ }
+ }
+ }
+ else if (quoteCategory) {
+ apiResponse = quoteFromCategory[quoteCategory];
+ isCustomQuote = true;
+ }
+
+ if(!isCustomQuote) {
+ apiResponse = await requestApi(url);
+ }
+
+ if (!apiResponse || apiResponse.length === 0) {
+ return [];
+ }
+
+ // Get array of authors in alphabetical order and without duplicates
+ const authors = [...new Set(apiResponse.map(quote => quote.author).sort())];
+
+ return authors;
+ } catch (error) {
+ throw error;
+ }
+}
+
module.exports = {
getQuote,
+ getAuthors
};