handleClick(action)}>
diff --git a/client/src/components/Post/PostCard.jsx b/client/src/components/Post/PostCard.jsx
index 39d1aa30..ef873803 100644
--- a/client/src/components/Post/PostCard.jsx
+++ b/client/src/components/Post/PostCard.jsx
@@ -432,6 +432,29 @@ return {
{rejectedBy?.length}
+ {(upQuote > 0 || downQuote > 0) && (
+ <>
+ |
+
+
+ >
+ )}
{interactions.length} interactions
diff --git a/client/src/components/customExpansionPanel.jsx b/client/src/components/customExpansionPanel.jsx
index 8a8a9e39..13c0ae41 100644
--- a/client/src/components/customExpansionPanel.jsx
+++ b/client/src/components/customExpansionPanel.jsx
@@ -1,4 +1,3 @@
-// TODO: Fix links to have href
import React from 'react'
import PropTypes from 'prop-types'
// @material-ui/core components
@@ -63,10 +62,9 @@ export default function CustomAccordion({ collapses, active: activeProp }) {
}}
>
- {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
{title}
@@ -89,8 +87,13 @@ export default function CustomAccordion({ collapses, active: activeProp }) {
display: 'flex', alignItems: 'center', flexDirection: 'row', marginRight: '2%',
}}
>
- {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
- handleCopy(DOMAIN + url, key)}>
+ {
+ e.preventDefault();
+ handleCopy(DOMAIN + url, key);
+ }}
+ >
{activeKey === key ? (
{
+ const { redirectToIfValid, redirectToIfInvalid } = options;
+ const dispatch = useDispatch();
+ const history = useHistory();
+
+ useEffect(() => {
+ const isValid = tokenValidator(dispatch);
+
+ if (isValid && redirectToIfValid) {
+ history.push(redirectToIfValid);
+ } else if (!isValid && redirectToIfInvalid) {
+ history.push(redirectToIfInvalid);
+ }
+ }, [dispatch, history, redirectToIfValid, redirectToIfInvalid]);
+
+ // Return the current validation state
+ return tokenValidator(dispatch);
+};
+
+export default useTokenValidation;
diff --git a/client/src/layouts/Scoreboard.jsx b/client/src/layouts/Scoreboard.jsx
index 5de35f4e..70e711cc 100644
--- a/client/src/layouts/Scoreboard.jsx
+++ b/client/src/layouts/Scoreboard.jsx
@@ -14,7 +14,7 @@ import CssBaseline from '@material-ui/core/CssBaseline'
import appRoutes from '../routes'
import styles from 'assets/jss/material-dashboard-pro-react/layouts/adminStyle'
-import { tokenValidator } from 'store/user'
+import useTokenValidation from '../hooks/useTokenValidation'
import { useDispatch, useSelector } from 'react-redux'
import { SET_SNACKBAR } from 'store/ui'
import Snackbar from 'mui-pro/Snackbar/Snackbar'
@@ -65,11 +65,8 @@ function Scoreboard(props) {
const routes = getRoutes(appRoutes)
- // TODO: Abstract validation into custom hook
- useEffect(() => {
- tokenValidator(dispatch)
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [])
+ useTokenValidation()
+
useEffect(() => {
const {
diff --git a/client/src/views/PasswordResetPage/PasswordResetPage.jsx b/client/src/views/PasswordResetPage/PasswordResetPage.jsx
index a14495e2..83acace7 100644
--- a/client/src/views/PasswordResetPage/PasswordResetPage.jsx
+++ b/client/src/views/PasswordResetPage/PasswordResetPage.jsx
@@ -1,7 +1,7 @@
import React from 'react'
import { makeStyles } from '@material-ui/core/styles'
import GridContainer from 'mui-pro/Grid/GridContainer'
-import { tokenValidator } from 'store/user'
+import useTokenValidation from '../../hooks/useTokenValidation'
import { useDispatch } from 'react-redux'
import { useHistory, useLocation } from 'react-router-dom'
import styles from 'assets/jss/material-dashboard-pro-react/views/loginPageStyle'
@@ -26,11 +26,8 @@ export default function PasswordResetPage() {
})
const [updateUserPassword] = useMutation(UPDATE_USER_PASSWORD)
const isValidToken = data && data.verifyUserPasswordResetToken
- // TODO: Abstract validation into custom hook
- React.useEffect(() => {
- if (tokenValidator(dispatch)) history.push('/search')
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [])
+ useTokenValidation({ redirectToIfValid: '/search' })
+
const classes = useStyles()
const handleSubmit = async (values) => {
diff --git a/server/app/data/resolvers/mutations/userInvite/sendUserInviteApproval.js b/server/app/data/resolvers/mutations/userInvite/sendUserInviteApproval.js
index d1240581..64d34e95 100644
--- a/server/app/data/resolvers/mutations/userInvite/sendUserInviteApproval.js
+++ b/server/app/data/resolvers/mutations/userInvite/sendUserInviteApproval.js
@@ -87,8 +87,7 @@ export const sendUserInviteApproval = (pubsub) => {
}
const email = await sendGridEmail(mailOptions);
- // *** TODO test to see if email was sent ***
- if (email) {
+ if (email && email.success) {
return {
code: 'SUCCESS',
message: `User sign-up invite sent successfully to ${mailTo}.`,
diff --git a/server/app/data/resolvers/queries/post/getPost.js b/server/app/data/resolvers/queries/post/getPost.js
index 718224b6..5f1f1867 100644
--- a/server/app/data/resolvers/queries/post/getPost.js
+++ b/server/app/data/resolvers/queries/post/getPost.js
@@ -1,8 +1,18 @@
+import mongoose from 'mongoose';
import PostModel from '../../models/PostModel';
export const getPost = (pubsub) => {
return async (_, args, context) => {
- const post = await PostModel.findById(args.postId);
+ let post;
+
+ // Check if the provided postId is a valid MongoDB ObjectId
+ if (mongoose.Types.ObjectId.isValid(args.postId)) {
+ post = await PostModel.findById(args.postId);
+ } else {
+ // If it's not a valid ObjectId, assume it's a short URL ID (urlId)
+ post = await PostModel.findOne({ urlId: args.postId });
+ }
+
if (!post || post.deleted) {
if (context && context.res) {
context.res.status(404);
diff --git a/server/app/data/resolvers/queries/user/findUserById.js b/server/app/data/resolvers/queries/user/findUserById.js
index 9cb9fb4c..78a17359 100644
--- a/server/app/data/resolvers/queries/user/findUserById.js
+++ b/server/app/data/resolvers/queries/user/findUserById.js
@@ -1,6 +1,8 @@
import { isUndefined } from 'lodash';
import UserModel from '../../models/UserModel';
import VotesModel from '../../models/VoteModel';
+import PostModel from '../../models/PostModel';
+import QuoteModel from '../../models/QuoteModel';
import * as utils from '../../utils';
export const findUserById = () => {
@@ -31,15 +33,20 @@ export const findUserById = () => {
userId = user._id;
// get user total votes
- const userVotes = await VotesModel.find({ _userId: userId });
+ const userVotes = await VotesModel.find({ userId, deleted: false });
user.vote_cast = isUndefined(userVotes) ? 0 : userVotes.length;
user._followingId = utils.uniqueArrayObjects(user._followingId);
user._followersId = utils.uniqueArrayObjects(user._followersId);
- // TODO: calculate user points
- user.points = 0;
+ // Calculate user points
+ // 10 pts per post, 5 pts per quote, 1 pt per vote cast
+ const postCount = await PostModel.countDocuments({ userId, deleted: false });
+ const quoteCount = await QuoteModel.countDocuments({ quoter: userId, deleted: false });
+ const voteCount = user.vote_cast;
+
+ user.points = (postCount * 10) + (quoteCount * 5) + voteCount;
return user;
} catch (err) {
diff --git a/test_fetch.js b/test_fetch.js
new file mode 100644
index 00000000..08a882aa
--- /dev/null
+++ b/test_fetch.js
@@ -0,0 +1,26 @@
+const postId = "e_6A4M";
+const graphqlUrl = 'https://api.quote.vote/graphql';
+const graphqlQuery = {
+ query: `
+ query post($postId: String!) {
+ post(postId: $postId) {
+ _id
+ title
+ text
+ url
+ creator {
+ name
+ avatar
+ }
+ }
+ }
+ `,
+ variables: { postId },
+};
+fetch(graphqlUrl, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(graphqlQuery),
+}).then(res => res.json()).then(data => console.log(JSON.stringify(data, null, 2))).catch(err => console.error(err));
diff --git a/test_get_posts.js b/test_get_posts.js
new file mode 100644
index 00000000..d39b5327
--- /dev/null
+++ b/test_get_posts.js
@@ -0,0 +1,9 @@
+const graphqlUrl = 'https://api.quote.vote/graphql';
+fetch(graphqlUrl, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ query: '{ posts(limit: 5, offset: 0, searchKey: "", sortOrder: "newest") { entities { _id title url } } }' })
+})
+.then(r => r.json())
+.then(d => console.log(JSON.stringify(d, null, 2)))
+.catch(console.error);
diff --git a/test_regex.js b/test_regex.js
new file mode 100644
index 00000000..bd52eb7e
--- /dev/null
+++ b/test_regex.js
@@ -0,0 +1,5 @@
+const fs = require('fs');
+let html = fs.readFileSync('client/index.html', 'utf8');
+const ogTitle = 'Test Title';
+html = html.replace(//, ``);
+console.log("Matched:", html.includes(ogTitle));
diff --git a/test_regex2.js b/test_regex2.js
new file mode 100644
index 00000000..7c604f82
--- /dev/null
+++ b/test_regex2.js
@@ -0,0 +1,16 @@
+const ogTitle = 'Replaced Title';
+const testCases = [
+ '',
+ '',
+ '',
+ '',
+];
+
+const regex = /]*property=["']?og:title["']?[^>]*>/i;
+
+testCases.forEach(html => {
+ const result = html.replace(regex, ``);
+ console.log("Original:", html);
+ console.log("Replaced:", result);
+ console.log("---");
+});