From 3579d01ee9cbf45e8d9ef918b7f434172a13717e Mon Sep 17 00:00:00 2001 From: Jay Timbol Date: Thu, 1 Jan 2026 23:17:01 -0600 Subject: [PATCH 1/7] visit counter endpoint + model, display on homepage --- api/main_endpoints/models/HomepageVisit.js | 14 ++ api/main_endpoints/routes/Homepage.js | 29 ++++ src/APIFunctions/HomepageVisit.js | 40 +++++ src/Pages/Home/Home.js | 175 ++++++++++----------- 4 files changed, 164 insertions(+), 94 deletions(-) create mode 100644 api/main_endpoints/models/HomepageVisit.js create mode 100644 api/main_endpoints/routes/Homepage.js create mode 100644 src/APIFunctions/HomepageVisit.js diff --git a/api/main_endpoints/models/HomepageVisit.js b/api/main_endpoints/models/HomepageVisit.js new file mode 100644 index 000000000..1e1caa59a --- /dev/null +++ b/api/main_endpoints/models/HomepageVisit.js @@ -0,0 +1,14 @@ +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const HomepageVisitSchema = new Schema( + { + visitCount: { + type: Number, + default: 0 + } + }, + { collection: 'HomepageVisit' } +); + +module.exports = mongoose.model('HomepageVisit', HomepageVisitSchema); \ No newline at end of file diff --git a/api/main_endpoints/routes/Homepage.js b/api/main_endpoints/routes/Homepage.js new file mode 100644 index 000000000..d6c962c1e --- /dev/null +++ b/api/main_endpoints/routes/Homepage.js @@ -0,0 +1,29 @@ +const express = require ('express'); +const router = express.Router(); +const visitCounter = require('../models/HomepageVisit'); +const { BAD_REQUEST, OK } = require('../../util/constants').STATUS_CODES; + +router.post('/visit', async (req, res) => { + try { + await visitCounter.findOneAndUpdate( + {}, + {$inc: {visitCount: 1}}, + { new: true, upsert: true } + ); + res.sendStatus(OK); + } catch (error) { + res.sendStatus(BAD_REQUEST); + } +}); + +router.get('/count', async (req, res) => { + try { + const response = await visitCounter.findOne({}); + const visitCount = response ? response.visitCount : 0; + return res.status(OK).send({visitCount}); + } catch (error) { + return res.sendStatus(BAD_REQUEST); + } +}) + +module.exports = router; \ No newline at end of file diff --git a/src/APIFunctions/HomepageVisit.js b/src/APIFunctions/HomepageVisit.js new file mode 100644 index 000000000..7eac3ac15 --- /dev/null +++ b/src/APIFunctions/HomepageVisit.js @@ -0,0 +1,40 @@ +import { ApiResponse } from './ApiResponses'; +import { BASE_API_URL } from '../Enums'; + +export async function incrementVisitCount() { + let status = new ApiResponse(); + try { + await fetch (BASE_API_URL + 'api/Homepage/visit', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }); + if (res.ok) { + status.responseData = result; + } else { + status.error = true; + } + } catch (err) { + status.responseData = err; + status.error = true; + } + return status; +} + +export async function getVisitCount() { + let status = new ApiResponse(); + try { + const res = await fetch (BASE_API_URL + 'api/Homepage/count'); + if (res.ok) { + const result = await res.json(); + status.responseData = result; + } else { + status.error = true; + } + } catch (err) { + status.responseData = err; + status.error = true; + } + return status; +} \ No newline at end of file diff --git a/src/Pages/Home/Home.js b/src/Pages/Home/Home.js index d58a79f8c..eb1cc7821 100755 --- a/src/Pages/Home/Home.js +++ b/src/Pages/Home/Home.js @@ -1,126 +1,113 @@ -import React, { useEffect, useState} from 'react'; +import React, { useEffect, useState, useMemo } from 'react'; +import { Link } from 'react-router-dom'; // Use Link for internal navigation import Footer from '../../Components/Footer/Footer.js'; -import './Home.css'; - import { getAd } from '../../APIFunctions/Advertisement.js'; import { getHomeImage } from '../../APIFunctions/Image.js'; +import { incrementVisitCount, getVisitCount } from '../../APIFunctions/HomepageVisit.js'; +import './Home.css'; -const Home = () => { - - const [message, setMessage] = useState(''); - const [showMessage, setShowMessage] = useState(false); - const [showAll, setShowAll] = useState(false); - const [homeImageUrl, setHomeImageUrl] = useState(''); - const [homeImageAlt, setHomeImageAlt] = useState(''); - - async function getMessage() { - try { - const messageData = await getAd(); - const adsList = messageData.responseData; - setMessage(adsList['message']); - setShowMessage(true); - } catch { - setMessage(''); - } - } +const DEFAULT_IMAGE = 'https://raw.githubusercontent.com/thebeninator/Clark/refs/heads/add_comp_homepage/public/images/compressed2.jpg'; - async function loadHomeImage() { - const DEFAULT_IMAGE = 'https://raw.githubusercontent.com/thebeninator/Clark/refs/heads/add_comp_homepage/public/images/compressed2.jpg'; - const response = await getHomeImage(); - let url = DEFAULT_IMAGE; - let alt = 'sce club image'; - if (!response.error) { - url = response.responseData.url; - alt = response.responseData.alt; - } - setHomeImageUrl(url); - setHomeImageAlt(alt); - } +const Home = () => { + const [isLoaded, setIsLoaded] = useState(false); + const [data, setData] = useState({ + message: '', + imageUrl: DEFAULT_IMAGE, + imageAlt: 'SCE Club Image', + visitCount: 0, + }); useEffect(() => { - getMessage(); - loadHomeImage(); - setTimeout(() => setShowAll(true), 100); + const fetchData = async () => { + try { + const [adRes, imgRes, _] = await Promise.all([ + getAd(), + getHomeImage(), + incrementVisitCount(), + ]); + + const visitRes = await getVisitCount(); + + setData({ + message: adRes.responseData?.message || '', + imageUrl: imgRes.error ? DEFAULT_IMAGE : imgRes.responseData.url, + imageAlt: imgRes.error ? 'SCE Club Image' : imgRes.responseData.alt, + visitCount: visitRes.responseData?.visitCount || 0, + }); + } catch (err) { + console.error("Home Page failed to load full data", err); + } finally { + setIsLoaded(true); + } + }; + + fetchData(); }, []); - function isValidUrl(str) { - try { - new URL(str); - return true; - } catch (_) { - return false; - } - } + const renderedMessage = useMemo(() => { + if (!data.message) return null; - function renderMessageWithLinks(message) { - if (!message) { - return null; - } - return message.split(/(https?:\/\/[^\s]+)/g).map((part, index) => { - if (isValidUrl(part)) { + return data.message.split(/(https?:\/\/[^\s]+)/g).map((part, i) => { + const isUrl = /https?:\/\/[^\s]+/.test(part); + if (isUrl) { return ( - + {part} ); } - return {part}; + return {part}; }); - } + }, [data.message]); + + // only include values passed in that evaluate to true + const getClassName = (...classes) => classes.filter(Boolean).join(' '); return ( -
-
-
-
-

- The Software and Computer Engineering Society +
+
+ +
+
+

+ Software and Computer
Engineering Society

- -

+

SJSU's Largest Engineering Club

-
- - - - - - - +
+ + Learn More + + + Join Us! +
-
+
- -
-
+
+
{homeImageAlt} - -
-
- {renderMessageWithLinks(message)} + + {data.message && ( +
+
+ {renderedMessage} +
-
+ )}
-
+
+
+ +
+ Global Visits: {data.visitCount.toLocaleString()}