diff --git a/README.md b/README.md index 027584c5e..07b0be24f 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,11 @@ Assignment 4 - Components === +Jacquelyn Lopez -Due: October 4th, by 11:59 AM. +I was given an extension from the professor. -For this assignment you will re-implement the client side portion of *either* A2 or A3 using either React or Svelte components. If you choose A3 you only need to use components for the data display / updating; you can leave your login UI as is. +## Jackie's Art Museum Using React -[Svelte Tutorial](https://github.com/cs4241-21a/cs4241-21a.github.io/blob/main/using_svelte.md) -[React Tutorial](https://github.com/cs4241-21a/cs4241-21a.github.io/blob/main/using_react.md) +Glitch https://a4-jlopez-codes.glitch.me -This project can be implemented on any hosting service (Glitch, DigitalOcean, Heroku etc.), however, you must include all files in your GitHub repo so that the course staff can view them. - -Deliverables ---- - -Do the following to complete this assignment: - -1. Implement your project with the above requirements. -3. Test your project to make sure that when someone goes to your main page on Glitch/Heroku/etc., it displays correctly. -4. Ensure that your project has the proper naming scheme `a4-firstname-lastname` so we can find it. -5. Fork this repository and modify the README to the specifications below. Be sure to add *all* project files. -6. Create and submit a Pull Request to the original repo. Name the pull request using the following template: `a4-firstname-lastname`. - -Sample Readme (delete the above when you're ready to submit, and modify the below so with your links and descriptions) ---- - -## Your Web Application Title - -your hosting link e.g. http://a4-charlieroberts.glitch.me - -Include a very brief summary of your project here and what you changed / added to assignment #3. Briefly (3–4 sentences) answer the following question: did the new technology improve or hinder the development experience? - -Unlike previous assignments, this assignment will be solely graded on whether or not you successfully complete it. Partial credit will be generously given. +I based off this assignment from A2 by implementing React. The easiest way for me to do it was using Glitch's already-made react template. I believe it was so much easier using react and it felt like I had to use a lot less code. Everything should work. \ No newline at end of file diff --git a/components/router.jsx b/components/router.jsx new file mode 100644 index 000000000..be13952ac --- /dev/null +++ b/components/router.jsx @@ -0,0 +1,19 @@ +import * as React from "react"; +import { Switch, Route, Router } from "wouter"; +import Home from "../pages/home"; +import About from "../pages/about"; + +/** +* The router is imported in app.jsx +* +* Our site just has two routes in it–Home and About +* Each one is defined as a component in /pages +* We use Switch to only render one route at a time https://github.com/molefrog/wouter#switch- +*/ + +export default () => ( + + + + +); diff --git a/components/seo.jsx b/components/seo.jsx new file mode 100644 index 000000000..24ccb174a --- /dev/null +++ b/components/seo.jsx @@ -0,0 +1,37 @@ +import * as React from "react"; +import SEO from "../seo.json"; +import { Helmet } from 'react-helmet-async'; + +const Seo = () => { + // If url is set to 'glitch-default', we use the hostname for the current page + // Otherwise we use the value set in seo.json + const url = SEO.url === 'glitch-default' ? window.location.hostname : SEO.url + + // React Helmet manages the content of the page head such as meta tags + // We use the async package https://github.com/staylor/react-helmet-async + return + {SEO.title} + + + + + + + + + + + + +}; + +export default Seo; diff --git a/hooks/prefers-reduced-motion.jsx b/hooks/prefers-reduced-motion.jsx new file mode 100644 index 000000000..0c48f2bc3 --- /dev/null +++ b/hooks/prefers-reduced-motion.jsx @@ -0,0 +1,22 @@ +import React from "react"; + +// User system can indicate reduced motion setting https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion + +const query = "(prefers-reduced-motion: no-preference)"; + +export function usePrefersReducedMotion() { + const [prefersReducedMotion, setPrefersReducedMotion] = React.useState( + !window.matchMedia(query).matches + ); + React.useEffect(() => { + const mediaQueryList = window.matchMedia(query); + const listener = event => { + setPrefersReducedMotion(!event.matches); + }; + mediaQueryList.addListener(listener); + return () => { + mediaQueryList.removeListener(listener); + }; + }, []); + return prefersReducedMotion; +} diff --git a/hooks/wiggle.jsx b/hooks/wiggle.jsx new file mode 100644 index 000000000..e23bdf259 --- /dev/null +++ b/hooks/wiggle.jsx @@ -0,0 +1,55 @@ +import React, { useState, useCallback } from "react"; +import { useSpring } from "react-spring"; +import { usePrefersReducedMotion } from "./prefers-reduced-motion"; +// Heavily inspired by Josh Comeau: https://www.joshwcomeau.com/react/boop/ 💖 + +// Wiggle function accepts various parameters specifying properties for the animation +export function useWiggle({ + x = 0, + y = 0, + rotation = 0, + scale = 1, + timing = 150, + springConfig = { + tension: 300, + friction: 10 + } +}) { + // Accessibility setting from the user system indicating that they prefer to minimize motion + const prefersReducedMotion = usePrefersReducedMotion(); + + // Declare state variable isActive, set initially to false + const [isActive, setIsActive] = useState(false); + + // We offload the actual animation to spring: https://www.react-spring.io/docs/hooks/use-spring + const style = useSpring({ + transform: isActive + ? `translate(${x}px, ${y}px) rotate(${rotation}deg) scale(${scale})` + : `translate(0px, 0px) rotate(0deg) scale(1)`, + config: springConfig + }); + + // Timing parameter determines how long the wiggle lasts using browser setTimeout function + // React useEffect function https://reactjs.org/docs/hooks-effect.html + React.useEffect(() => { + if (!isActive) { + return; + } + const timeoutId = window.setTimeout(() => { + setIsActive(false); + }, timing); + return () => { + window.clearTimeout(timeoutId); + }; + }, [isActive]); // Continue wiggle until isActive is set false when timeout elapses + + // Set wiggle to active when the triggering event occurs - will be set false when effect completes above + const trigger = useCallback(() => { + setIsActive(true); + }, []); + + let appliedStyle = prefersReducedMotion ? {} : style; + + // Return animation style effect and function to apply on trigger in page + return [appliedStyle, trigger]; +} diff --git a/package.json b/package.json new file mode 100644 index 000000000..1c9b0301b --- /dev/null +++ b/package.json @@ -0,0 +1,34 @@ +{ + "name": "glitch-hello-react", + "version": "0.0.5", + "description": "A simple React single page app, built with Vite and React.", + "scripts": { + "start": "vite", + "build": "vite build", + "serve": "vite preview" + }, + "dependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0", + "react-spring": "^9.4.5", + "wouter": "^2.7.4", + "react-helmet-async": "^1.3.0", + "@vitejs/plugin-react": "^1.3.2" + }, + "devDependencies": { + "vite": "^2.4.4" + }, + "browserslist": [ + ">0.2%", + "not dead" + ], + "engines": { + "node": "12.x" + }, + "repository": { + "url": "https://glitch.com/edit/#!/glitch-hello-react" + }, + "glitch": { + "projectType": "generated_static" + } + } \ No newline at end of file diff --git a/src/AppointForm.jsx b/src/AppointForm.jsx new file mode 100644 index 000000000..af9df09b2 --- /dev/null +++ b/src/AppointForm.jsx @@ -0,0 +1,33 @@ +import React, {useState} from 'react'; + +const FormTwo = (props) => { + + const initAppoint = {id: null, studentID: '', name: '', appointment: ''}; + + const [appoint, setAppoint] = useState(initAppoint); + + const handleChange = e => { + const {name, value} = e.target; + setAppoint({...appoint, [name]: value}); + } + + const handleSubmit = e => { + e.preventDefault(); + if (appoint.studentID && appoint.name && appoint.appointment) { + handleChange(e, props.addAppoint(appoint)); + } + } + + + + return ( +
+ + + + +
+ ) +} + +export default FormTwo; \ No newline at end of file diff --git a/src/EditForm.jsx b/src/EditForm.jsx new file mode 100644 index 000000000..5a47909ad --- /dev/null +++ b/src/EditForm.jsx @@ -0,0 +1,32 @@ +import React, {useState, useEffect} from 'react'; + +const FormOne = (props) => { + + useEffect(() => { + setAppoint(props.currentAppoint) + }, [props]) + + const [appoint, setAppoint] = useState(props.currentAppoint); + + const handleChange = e => { + const {name, value} = e.target; + setAppoint({...appoint, [name]: value}); + } + + const handleSubmit = e => { + e.preventDefault(); + if (appoint.studentID && appoint.name && appoint.appointment) props.updateAppoint(appoint); + } + + return ( +
+ + + + + +
+ ) +} + +export default FormOne; \ No newline at end of file diff --git a/src/Table.jsx b/src/Table.jsx new file mode 100644 index 000000000..490f6509c --- /dev/null +++ b/src/Table.jsx @@ -0,0 +1,43 @@ +import React from 'react'; + +const UserTable = (props) => { + return ( + + + + + + + + + + + + { props.appoints.length > 0 ? ( + props.appoints.map(appoint => { + const {id, studentID, name, appointment} = appoint; + return ( + + + + + + + + ) + }) + ) : ( + + + + ) + } + +
IDStudent ID #NameAppointment DateActions
{id}{studentID}{name}{appointment} + + +
No appointments made.
+ ) +} + +export default UserTable; \ No newline at end of file diff --git a/src/app.jsx b/src/app.jsx new file mode 100644 index 000000000..358008f26 --- /dev/null +++ b/src/app.jsx @@ -0,0 +1,143 @@ +import React, { useState, useEffect } from "react"; +import UserTable from "./Table.jsx"; +import './styles/styles.css'; +import FormTwo from "./AppointForm.jsx"; +import FormOne from "./EditForm.jsx"; + +const App = () => { + const [appoints, setAppoints] = useState(() => { + const savedAppoints = localStorage.getItem("Appoints"); + if (savedAppoints) { + return JSON.parse(savedAppoints); + } else { + return []; + } + }); + + useEffect(() => { + localStorage.setItem("Appoints", JSON.stringify(appoints)); + }, [appoints]); + + const addAppoint = (appoint) => { + var max = 0; + for(let i = 0; i < appoints.length; i++){ + if(appoints[i].id > max){ + max = appoints[i].id; + } + } + appoint.id = max + 1; + setAppoints([...appoints, appoint]); + }; + + const deleteAppoint = (id) => { + setAppoints(appoints.filter((appoint) => appoint.id !== id)); + }; + + const [editing, setEditing] = useState(false); + + const initialAppoint = { id: null, studentID: "", name: "", appointment: "" }; + + const [currentAppoint, setCurrentAppoint] = useState(initialAppoint); + + const editAppoint = (id, appoint) => { + setEditing(true); + setCurrentAppoint(appoint); + }; + + const updateAppoint = (newAppoint) => { + setAppoints( + appoints.map((appoint) => (appoint.id === currentAppoint.id ? newAppoint : appoint)) + ); + setCurrentAppoint(initialAppoint); + setEditing(false); + }; + + + return ( + +
+

+
+

+
+
+ {editing ? ( +
+

About El Movimiento

+ +
+ ) : ( +
+ +

About El Movimiento

+ + + + + + + + + +

+ Welcome to Jackie's Art Museum! As a "Chicana" herself, Jackie wanted to share with the world her passion for Chicano Art. +

+ + +

+ Known as El Movimiento, The Chicano Movement began in the late 1960's and early 1970s that advocated social and political + empowerment through a chicanismo / cultural nationalism. This was a way for Chicanos to form and develop a collective identity + that distinguised itself as Mexican-American to reject assimilation to the Euro-Centric American culture. +

+ + + +

+ Many of the exhibits here showcase some of the most inspiring art from popular Chicano Artists such as: + +

+

+ + + + + + + + + + + +

Registration

+

+ If you would like to visit our museum, please register below for an appointment. + Due to the increase of covid, we must take precautions and limit our capacity + for our safety and yours. +

+ + +
+ )} +
+
+ +
+
+
+ ); +}; + +export default App; \ No newline at end of file diff --git a/src/index.html b/src/index.html new file mode 100644 index 000000000..7ebe8eb39 --- /dev/null +++ b/src/index.html @@ -0,0 +1,16 @@ + + + + + + + Hello React! + + + + + +
+ + + \ No newline at end of file diff --git a/src/index.jsx b/src/index.jsx new file mode 100644 index 000000000..8887e58c7 --- /dev/null +++ b/src/index.jsx @@ -0,0 +1,5 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from './app.jsx'; + +ReactDOM.render(, document.getElementById('root')); \ No newline at end of file diff --git a/src/seo.json b/src/seo.json new file mode 100644 index 000000000..d356b5f34 --- /dev/null +++ b/src/seo.json @@ -0,0 +1,7 @@ +{ + "glitch-help-instructions": "For a custom domain, change the 'url' parameter from 'glitch-default' to your domain _without_ a traling slash, like 'https://www.example.com'", + "title": "Jacquelyn Lopez A4", + "description": "I decided to choose to do Components for my A4", + "url": "https://a4-jlopez-codes.glitch.me" + } + \ No newline at end of file diff --git a/src/styles.css b/src/styles.css new file mode 100644 index 000000000..96614f3b8 --- /dev/null +++ b/src/styles.css @@ -0,0 +1,172 @@ + +title { + + font-family: 'Open Sans', sans-serif; + } + h1 { + font-size: 40px; + text-align: center; + font-family: 'Pacifico', cursive; + } + h2 { + font-family: 'Open Sans', sans-serif; + color: 555555; + } + p { + font-family: 'Open Sans', sans-serif; + font-size: 20px; + } + + table { + width: 120%; + background-color: white; + } + + table, td, th { + border: 1px solid black; + font-family: 'Open Sans', sans-serif; + } + + th, td{ + background-color: #CC938F; + } + + + button { + font-family: 'Open Sans', sans-serif; + } + + body { + background-color: #A892D6; + } + + img{ + width: 100%; + height: 100%; + position: center; + + } + + + .solid { + width:70px; + length:70px; + margin: 50px 50px 50px 180px; + font-size:13px; + text-align: center; + font-family:sans-serif; + border-radius: 30%; + background-color: #D6D45A; + outline: 20px solid #D6D45A; + outline-offset: 0px ; + } + + .container { + display: flex; + grid-template-columns: 1fr 1fr 1fr 1fr; + + } + + .tableBtn { + padding: 4px 4px 4px 4px; + font-family: 'Open Sans', sans-serif; + } + + .edit { + background-color: #4CAF50; + } + + .delete { + background-color: #f44336; + } + + .pink { + background-color: #CC938F; + } + .yellow { + background-color: #D6C659; + } + .green { + background-color: #8CBFAB; + } + .purple { + background-color: #A892D6; + } + + + .blink-bg{ + color: #ffffff; + padding: 14px; + display: inline-flexbox; + border-radius: 5px; + animation: blinkingBackground 2s infinite; + } + @keyframes blinkingBackground{ + 0% { background-color: #CCA78F;} + 25% { background-color: #D6D45A;} + 50% { background-color: #8CBFAB;} + 75% { background-color: #4d7dc1;} + 100% { background-color: #B8717D;} + } + + .dot1 { + height: 50px; + width: 50px; + background-color: #CC938F; + border-radius: 50%; + display: inline-block; + } + .dot2 { + height: 50px; + width: 50px; + background-color: #D6C659; + border-radius: 50%; + display: inline-block; + } + .dot3 { + height: 50px; + width: 50px; + background-color: #4d7dc1; + border-radius: 50%; + display: inline-block; + } + .dot4 { + height: 50px; + width: 50px; + background-color: #8CBFAB; + border-radius: 50%; + display: inline-block; + } + + .smdot1 { + height: 25px; + width: 25px; + background-color: #CC938F; + border-radius: 50%; + display: inline-block; + } + .smdot2 { + height:25px; + width: 25px; + background-color: #D6C659; + border-radius: 50%; + display: inline-block; + } + .smdot3 { + height: 25px; + width: 25px; + background-color: #4d7dc1; + border-radius: 50%; + display: inline-block; + } + .smdot4 { + height: 25px; + width: 25px; + background-color: #8CBFAB; + border-radius: 50%; + display: inline-block; + } + + #Title{ + text-align: left; + } \ No newline at end of file diff --git a/vite.config,js b/vite.config,js new file mode 100644 index 000000000..d4bb1911e --- /dev/null +++ b/vite.config,js @@ -0,0 +1,16 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + build: { + outDir: "build" + }, + server: { + strictPort: true, + hmr: { + port: 443 // Run the websocket server on the SSL port + } + } +}); diff --git a/watch.json b/watch.json new file mode 100644 index 000000000..a8125f302 --- /dev/null +++ b/watch.json @@ -0,0 +1,18 @@ +{ + "install": { + "include": [ + "^package\\.json$" + ], + "exclude": [] + }, + "restart": { + "include": [ + "^backend/" + ], + "exclude": [ + "src/" + ] + }, + "noSavedEvents": true + } + \ No newline at end of file