documentation and README.md updates.
This commit is contained in:
@@ -1,80 +1,55 @@
|
|||||||
Focus on fleshing out and adding systems to my portfolio
|
<h1>My Portfolio</h1>
|
||||||
|
|
||||||
- Not just past projects, but scripts to easily add new projects.
|
URL: https://joshashton.dev
|
||||||
- Responsive design
|
|
||||||
- Resume page
|
|
||||||
- SLCC e-portfolio
|
|
||||||
|
|
||||||
The idea is to showcase my full-stack development skills in a future-ready manner.
|
|
||||||
|
|
||||||
Timeline:
|
|
||||||
Week 1: 17/12/24 - 23/12/24 (Planning for the future)
|
|
||||||
|
|
||||||
- [x] Refactor and reorder the HTML & CSS.
|
|
||||||
- [x] Template HTML blocks for repeated components.
|
|
||||||
- [ ] Document classes, ID's, and other attributes.
|
|
||||||
- [ ] Script to easily add new projects (components).
|
|
||||||
- [x] Embeddable PDFs (research phase).
|
|
||||||
|
|
||||||
Week 2: 24/12/24 - 30/12/24 (Adding key information)
|
|
||||||
|
|
||||||
- [x] Resume page (HTML and PDF options).
|
|
||||||
- [ ] Convert SLCC e-portfolio to my own website
|
|
||||||
- [ ] Meet required pages and content
|
|
||||||
- [ ] Embed PDF course reflections/signature assignments.
|
|
||||||
- [x] Contact page.
|
|
||||||
- [x] Implement MailTrap for sending me automated emails when there is a contact form submission.
|
|
||||||
|
|
||||||
Week 3: 31/12/24 - 6/12/24
|
|
||||||
|
|
||||||
- [ ] Refactor & polish codebase.
|
|
||||||
- [ ] Update documentation.
|
|
||||||
- [ ] Demo's of various projects.
|
|
||||||
- [ ] Security:
|
|
||||||
- [ ] Blacklist IPs which access /wp-admin and /wp-login.
|
|
||||||
- [ ] Separate user for self-hosting, with reduced permissions.
|
|
||||||
- [ ] Dockerize
|
|
||||||
- [ ] Blog page (stretch goal).
|
|
||||||
|
|
||||||
Major Bugs (UI, UX, or Functionality):
|
|
||||||
|
|
||||||
- [x] Breathing animation of background gradient stopped. (v.0.0.1)
|
|
||||||
- [x] Add state to skill accordions for mobile. (v.0.0.1)
|
|
||||||
- [x] Alignment of project card on mobile. (v.0.0.1)
|
|
||||||
- [x] PDF viewer width is constant, doesn't respond to resizing (or setting the width in CSS). (v.0.0.1)
|
|
||||||
- [ ] More prominent slide indicator for projects (or n/10 counter, or some similar solution). (v.0.0.2)
|
|
||||||
|
|
||||||
Features
|
|
||||||
|
|
||||||
- [x] Refactor and reorder the HTML & CSS. (v.0.0.1)
|
|
||||||
- [x] Template HTML blocks for repeated components. (v.0.0.1)
|
|
||||||
- [x] Resume page (HTML and PDF options). (v.0.0.1)
|
|
||||||
- [x] Contact page. (v.0.0.1)
|
|
||||||
- [x] Proper 404 page (v.0.0.1)
|
|
||||||
- [ ] Security:
|
|
||||||
- [ ] Separate user for self-hosting, with reduced permissions. (v.0.0.1)
|
|
||||||
- [ ] Dockerize (v.0.0.1)
|
|
||||||
|
|
||||||
Future Features:
|
|
||||||
|
|
||||||
- [ ] Convert SLCC e-portfolio to my own website (v.0.0.2)
|
|
||||||
- [ ] Read in data for portfolio from a DB or preferably a plain text files. (v.0.0.2)
|
|
||||||
- [ ] Script to easily add new projects. (v.0.0.2)
|
|
||||||
- [ ] Demo's of various projects. (embed Java Swing app or Flutter app directly into portfolio). (v.0.0.2)
|
|
||||||
- [ ] Blog page. (v.0.0.3)
|
|
||||||
|
|
||||||
Refactoring:
|
|
||||||
|
|
||||||
- [ ] Rewrite CSS into components/modules (v.0.0.2)
|
|
||||||
|
|
||||||
Minor Bugs:
|
|
||||||
|
|
||||||
- [ ] Contact form submission centers entire section. (v.0.0.2)
|
|
||||||
- [ ] Profile picture animation is slightly cutoff on mobile. (v.0.0.2)
|
|
||||||
|
|
||||||
|
|
||||||
|
Core technologies are React and Node.js, using swiper as well.
|
||||||
|
See:
|
||||||
|
- https://react.dev/learn
|
||||||
|
- https://nodejs.org/en/learn/getting-started/introduction-to-nodejs
|
||||||
|
- https://swiperjs.com/react
|
||||||
|
|
||||||
COLORSCHEMES:
|
<br>
|
||||||
|
|
||||||
|
<h2>Documentation</h2>
|
||||||
|
|
||||||
|
<h3>Setup</h3>
|
||||||
|
|
||||||
|
<h4>Development Environment</h4>
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone https://github.com/quaxlyqueen/portfolio
|
||||||
|
cd portfolio
|
||||||
|
npm install
|
||||||
|
node src/server.js &
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
<h4>Production Environment</h4>
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone https://github.com/quaxlyqueen/portfolio
|
||||||
|
cd portfolio
|
||||||
|
npm run build
|
||||||
|
cd dist
|
||||||
|
node ../src/server.js &
|
||||||
|
serve -s
|
||||||
|
```
|
||||||
|
|
||||||
|
<h3>Project structure</h3>
|
||||||
|
|
||||||
|
- `/main.jsx` is the root and router.
|
||||||
|
- `/App.jsx` is the primary page of my portfolio, and currently contains all application data. New projects are added here.
|
||||||
|
- `/Home.jsx`, `/Projects.jsx`, and `/Contact.jsx` are pages (or slides) of `/App.jsx`.
|
||||||
|
- `/components/*` are the components used to build the pages.
|
||||||
|
|
||||||
|
<h3>Styling guidelines:</h3>
|
||||||
|
|
||||||
|
<h4>Colorscheme data:</h4>
|
||||||
|
|
||||||
|
<h5>Purple</h5>
|
||||||
|
|
||||||
|
```
|
||||||
--dark-0: #170123;
|
--dark-0: #170123;
|
||||||
--dark-1: #39124e;
|
--dark-1: #39124e;
|
||||||
--dark-2: #5e3276;
|
--dark-2: #5e3276;
|
||||||
@@ -83,7 +58,11 @@ COLORSCHEMES:
|
|||||||
--accent-1: #9e75a8;
|
--accent-1: #9e75a8;
|
||||||
--accent-2: #c69bca;
|
--accent-2: #c69bca;
|
||||||
--text: #EBD2FF;
|
--text: #EBD2FF;
|
||||||
|
```
|
||||||
|
|
||||||
|
<h5>Green</h5>
|
||||||
|
|
||||||
|
```
|
||||||
--dark-0: #091A19;
|
--dark-0: #091A19;
|
||||||
--dark-1: #10211E;
|
--dark-1: #10211E;
|
||||||
--dark-2: #162722;
|
--dark-2: #162722;
|
||||||
@@ -94,3 +73,40 @@ COLORSCHEMES:
|
|||||||
--accent-1: #8FC973;
|
--accent-1: #8FC973;
|
||||||
--accent-2: #B8DAA6;
|
--accent-2: #B8DAA6;
|
||||||
--text: #E0EAD9;
|
--text: #E0EAD9;
|
||||||
|
```
|
||||||
|
|
||||||
|
<h2>TODO</h2>
|
||||||
|
|
||||||
|
<h3>Features</h3>
|
||||||
|
|
||||||
|
- [x] Refactor and reorder the HTML & CSS. (v.0.0.1)
|
||||||
|
- [x] Template HTML blocks for repeated components. (v.0.0.1)
|
||||||
|
- [x] Contact page. (v.0.0.1)
|
||||||
|
- [x] Proper 404 page (v.0.0.1)
|
||||||
|
- [ ] Resume page (HTML and PDF options). (v.0.0.1)
|
||||||
|
- [ ] Make resume 1 page (v.0.0.1)
|
||||||
|
- [ ] Security:
|
||||||
|
- [ ] Separate user for self-hosting, with reduced permissions. (v.0.0.1)
|
||||||
|
- [ ] Dockerize (v.0.0.1)
|
||||||
|
|
||||||
|
<h3>Future Features:</h3>
|
||||||
|
|
||||||
|
- [ ] Rewrite CSS into components/modules (v.0.0.2)
|
||||||
|
- [ ] Read in data for portfolio a plain text files. (v.0.0.2)
|
||||||
|
- [ ] Script to easily add new projects. (v.0.0.2)
|
||||||
|
- [ ] Demo's of various projects. (embed Java Swing app or Flutter app directly into portfolio). (v.0.0.2)
|
||||||
|
- [ ] Blog page. (v.0.0.3)
|
||||||
|
- [ ] Convert SLCC e-portfolio to my own website (v.0.0.4)
|
||||||
|
|
||||||
|
<h3>Bugs:</h3>
|
||||||
|
|
||||||
|
- [ ] /Home.jsx, Profile picture animation is slightly cutoff on mobile. (v.0.0.2)
|
||||||
|
- [X] /Projects.jsx, More prominent slide indicator for projects (or n/10 counter, or some similar solution). (v.0.0.2)
|
||||||
|
- [x] /Projects.jsx, Alignment of project card on mobile. (v.0.0.1)
|
||||||
|
- [ ] /Projects.jsx, cursor icon when hover on the slide buttons should be a pointer, not highlighter.
|
||||||
|
- [ ] /Contact.jsx, handle blank and spam inputs
|
||||||
|
- [ ] /Contact.jsx, Contact form submission centers entire section. (v.0.0.2)
|
||||||
|
- [ ] /ErrorPage.jsx, stylize the error page.
|
||||||
|
- [x] /Resume.jsx, PDF viewer width is constant, doesn't respond to resizing (or setting the width in CSS). (v.0.0.1)
|
||||||
|
- [x] /components/SkillCard.jsx, Add state to skill accordions for mobile. (v.0.0.1)
|
||||||
|
- [x] /main.css, Breathing animation of background gradient stopped. (v.0.0.1)
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
--dark-0: #170123;
|
|
||||||
--dark-1: #39124e;
|
|
||||||
--dark-2: #5e3276;
|
|
||||||
--dark-3: #84509f;
|
|
||||||
--primary: #4a245e;
|
|
||||||
--accent-1: #9e75a8;
|
|
||||||
--accent-2: #c69bca;
|
|
||||||
--text: #EBD2FF;
|
|
||||||
|
|
||||||
--dark-0: #091A19;
|
|
||||||
--dark-1: #10211E;
|
|
||||||
--dark-2: #162722;
|
|
||||||
--dark-3: #1C2E26;
|
|
||||||
--primary-1: #304334;
|
|
||||||
--primary: #6B825C;
|
|
||||||
--accent-0: #90D889;
|
|
||||||
--accent-1: #8FC973;
|
|
||||||
--accent-2: #B8DAA6;
|
|
||||||
--text: #E0EAD9;
|
|
||||||
+18
-23
@@ -5,27 +5,23 @@ import Projects from "./Projects";
|
|||||||
import Contact from "./Contact";
|
import Contact from "./Contact";
|
||||||
import "./main.css";
|
import "./main.css";
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* TODO: Read in data from a plain text file for easier editing in the future.
|
* Primary portfolio application. Utilizes swiper.js (via /components/VerticalSlider.jsx) to
|
||||||
|
* have a scrolling section to section effect.
|
||||||
|
*
|
||||||
|
* This is the primary place where (currently) application data is stored. This information will be read in
|
||||||
|
* from plain-text files in the future for ease of future editing. In the meantime, generated components,
|
||||||
|
* specifically the Skill Card and Project Card (see /components/SkillCard.jsx and /components/ProjectCard.jsx)
|
||||||
|
* get their data from here and thus any additions are made in this file.
|
||||||
|
*
|
||||||
|
* TODO: Read in data from a plain text file for easier editing in the future, and to separate domains.
|
||||||
*/
|
*/
|
||||||
export default function App() {
|
const App = () => {
|
||||||
// DATA VARIABLES
|
// DATA VARIABLES
|
||||||
/*
|
|
||||||
{
|
|
||||||
title: "",
|
|
||||||
descriptionA: "",
|
|
||||||
featureList: [
|
|
||||||
|
|
||||||
],
|
|
||||||
image: "",
|
|
||||||
imageGallery: null,
|
|
||||||
url: ""
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
const projectData = [
|
const projectData = [
|
||||||
{
|
{
|
||||||
title: "One AI",
|
title: "One AI",
|
||||||
descriptionA:
|
description:
|
||||||
"An end-to-end self-hosted Artificial Intelligence solution, using Meta's Ollama platform to run Large Language Models. Go was used for the backend as a wrapper API and the frontend is Flutter. Here are some of the implemented features:",
|
"An end-to-end self-hosted Artificial Intelligence solution, using Meta's Ollama platform to run Large Language Models. Go was used for the backend as a wrapper API and the frontend is Flutter. Here are some of the implemented features:",
|
||||||
featureList: [
|
featureList: [
|
||||||
"AES encryption of messages between client and server",
|
"AES encryption of messages between client and server",
|
||||||
@@ -39,7 +35,7 @@ export default function App() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Snake",
|
title: "Snake",
|
||||||
descriptionA:
|
description:
|
||||||
"A Snake clone in the command line, written in C#. Utilized a Queue data structure to keep track of the snake's body. Implemented features include:",
|
"A Snake clone in the command line, written in C#. Utilized a Queue data structure to keep track of the snake's body. Implemented features include:",
|
||||||
featureList: [
|
featureList: [
|
||||||
"basic pseudo-randomized obstacle generation",
|
"basic pseudo-randomized obstacle generation",
|
||||||
@@ -53,7 +49,7 @@ export default function App() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Battery Monitor",
|
title: "Battery Monitor",
|
||||||
descriptionA:
|
description:
|
||||||
"A battery monitor, written in C with a GUI written in Python. Utilizes the acpi Unix command to gather battery information. Implemented features incldue:",
|
"A battery monitor, written in C with a GUI written in Python. Utilizes the acpi Unix command to gather battery information. Implemented features incldue:",
|
||||||
featureList: [
|
featureList: [
|
||||||
"embed Unix commands in C to get the date, time, battery percentage, and battery status",
|
"embed Unix commands in C to get the date, time, battery percentage, and battery status",
|
||||||
@@ -68,7 +64,7 @@ export default function App() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "D&D Character Builder",
|
title: "D&D Character Builder",
|
||||||
descriptionA:
|
description:
|
||||||
"A Dungeons and Dragons character builder, following the 5th edition rules and creation process. A collaborative project creating a GUI in Java with Swing. Features include:",
|
"A Dungeons and Dragons character builder, following the 5th edition rules and creation process. A collaborative project creating a GUI in Java with Swing. Features include:",
|
||||||
featureList: [
|
featureList: [
|
||||||
"serialization of character data into custom filetype",
|
"serialization of character data into custom filetype",
|
||||||
@@ -81,7 +77,7 @@ export default function App() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Calculator",
|
title: "Calculator",
|
||||||
descriptionA:
|
description:
|
||||||
"A simple and themable calculator written in Python. This uses CustomTKinter for the GUI and the eva CLI package for the actual calculator functionality. Supports MacOS and Linux. Implemented features include:",
|
"A simple and themable calculator written in Python. This uses CustomTKinter for the GUI and the eva CLI package for the actual calculator functionality. Supports MacOS and Linux. Implemented features include:",
|
||||||
featureList: [
|
featureList: [
|
||||||
"custom theming read in from a global .theme file",
|
"custom theming read in from a global .theme file",
|
||||||
@@ -123,9 +119,6 @@ export default function App() {
|
|||||||
|
|
||||||
const slides = [
|
const slides = [
|
||||||
<Home
|
<Home
|
||||||
primaryText="Hi, I'm Josh Ashton."
|
|
||||||
secondaryText="Junior full-stack developer."
|
|
||||||
callToAction="Here's my resume."
|
|
||||||
image="/images/profile.jpg"
|
image="/images/profile.jpg"
|
||||||
skills={skillsData}
|
skills={skillsData}
|
||||||
/>,
|
/>,
|
||||||
@@ -135,3 +128,5 @@ export default function App() {
|
|||||||
|
|
||||||
return <VerticalSlider slides={slides} />;
|
return <VerticalSlider slides={slides} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
|
|||||||
+14
-3
@@ -1,9 +1,16 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* TODO: Add email validation function.
|
* Contact page or "slide". The contact form functionality is enabled by a NodeJS server,
|
||||||
|
* see /server.js. A basic form for getting contact information. Emails are sent to me@joshashton.dev
|
||||||
|
* from contact@joshashton.dev.
|
||||||
|
*
|
||||||
|
* TODO: Handle blank and spam inputs.
|
||||||
|
*
|
||||||
|
* Managed by /App.jsx.
|
||||||
|
* Interacts with /server.js.
|
||||||
*/
|
*/
|
||||||
export default function Contact() {
|
const Contact = () => {
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
name: "",
|
name: "",
|
||||||
emailAddress: "",
|
emailAddress: "",
|
||||||
@@ -12,6 +19,7 @@ export default function Contact() {
|
|||||||
|
|
||||||
const [isSubmitted, setIsSubmitted] = useState(false);
|
const [isSubmitted, setIsSubmitted] = useState(false);
|
||||||
|
|
||||||
|
// Update form data to always be up to date with the entries on the form.
|
||||||
const handleChange = (event) => {
|
const handleChange = (event) => {
|
||||||
setFormData({
|
setFormData({
|
||||||
...formData,
|
...formData,
|
||||||
@@ -19,6 +27,7 @@ export default function Contact() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Transmit data to the server's API for sending email.
|
||||||
const handleSubmit = async (event) => {
|
const handleSubmit = async (event) => {
|
||||||
event.preventDefault(); // Prevent default form submission behavior
|
event.preventDefault(); // Prevent default form submission behavior
|
||||||
setIsSubmitted(true);
|
setIsSubmitted(true);
|
||||||
@@ -85,3 +94,5 @@ export default function Contact() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Contact;
|
||||||
|
|||||||
+11
-1
@@ -1,6 +1,14 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
export default function ErrorPage({ errorCode, errorMessage }) {
|
/*
|
||||||
|
* An error page component, typically used for 404 errors, but available for use for any other errors.
|
||||||
|
*
|
||||||
|
* TODO: Stylize the error page.
|
||||||
|
*
|
||||||
|
* @param errorCode String
|
||||||
|
* @param errorMessage String
|
||||||
|
*/
|
||||||
|
const ErrorPage = ({ errorCode, errorMessage }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="error-page">
|
<div className="error-page">
|
||||||
@@ -14,3 +22,5 @@ export default function ErrorPage({ errorCode, errorMessage }) {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default ErrorPage;
|
||||||
|
|||||||
+40
-31
@@ -1,8 +1,19 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import SkillCard from "./components/SkillCard";
|
import SkillCard from "./components/SkillCard";
|
||||||
|
|
||||||
export default function Home({ primaryText, secondaryText, image, skills }) {
|
/**
|
||||||
|
* "Home" page/slide of my portfolio.
|
||||||
|
*
|
||||||
|
* Managed by /App.jsx.
|
||||||
|
* Data by /App.jsx.
|
||||||
|
*
|
||||||
|
* @param image String
|
||||||
|
* @param skills json, containing JSON values of String arrays.
|
||||||
|
*/
|
||||||
|
const Home = ({ image, skills }) => {
|
||||||
|
// Manage active card state, defaulting to languages card being active or open. Mobile only.
|
||||||
const [activeCardIndex, setActiveCardIndex] = useState(0);
|
const [activeCardIndex, setActiveCardIndex] = useState(0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div id="primary">
|
<div id="primary">
|
||||||
@@ -12,43 +23,39 @@ export default function Home({ primaryText, secondaryText, image, skills }) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="header-text">
|
<div className="header-text">
|
||||||
<div className="center-column">
|
<div className="center-column">
|
||||||
<h1>{primaryText}</h1>
|
<h1>Hi, I'm Josh Ashton.</h1>
|
||||||
<h3>{secondaryText}</h3>
|
<h3>Junior full-stack developer.</h3>
|
||||||
<a className="button" href="/resume" target="_blank">
|
<a className="button" href="/resume" target="_blank">
|
||||||
<div className="button-grid">Access my resume</div>
|
<div className="button-grid">Access my resume</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{skills.languages !== null ? (
|
<div id="skill-cards">
|
||||||
<div id="skill-cards">
|
<div id="card-grid">
|
||||||
<div id="card-grid">
|
<SkillCard
|
||||||
<SkillCard
|
header="Languages"
|
||||||
header="Languages"
|
symbol="code"
|
||||||
symbol="code"
|
list={skills.languages}
|
||||||
list={skills.languages}
|
isActive={activeCardIndex === 0}
|
||||||
isActive={activeCardIndex === 0}
|
onToggle={() => setActiveCardIndex(0)}
|
||||||
onToggle={() => setActiveCardIndex(0)}
|
/>
|
||||||
/>
|
<SkillCard
|
||||||
<SkillCard
|
header="Tools"
|
||||||
header="Tools"
|
symbol="terminal"
|
||||||
symbol="terminal"
|
list={skills.tools}
|
||||||
list={skills.tools}
|
isActive={activeCardIndex === 1}
|
||||||
isActive={activeCardIndex === 1}
|
onToggle={() => setActiveCardIndex(1)}
|
||||||
onToggle={() => setActiveCardIndex(1)}
|
/>
|
||||||
/>
|
<SkillCard
|
||||||
<SkillCard
|
header="Other"
|
||||||
header="Other"
|
symbol="hub"
|
||||||
symbol="hub"
|
list={skills.other}
|
||||||
list={skills.other}
|
isActive={activeCardIndex === 2}
|
||||||
isActive={activeCardIndex === 2}
|
onToggle={() => setActiveCardIndex(2)}
|
||||||
onToggle={() => setActiveCardIndex(2)}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
</div>
|
||||||
<p>Something here</p>
|
|
||||||
)}
|
|
||||||
<span className="material-icons down-indicator">
|
<span className="material-icons down-indicator">
|
||||||
keyboard_double_arrow_down
|
keyboard_double_arrow_down
|
||||||
</span>
|
</span>
|
||||||
@@ -56,3 +63,5 @@ export default function Home({ primaryText, secondaryText, image, skills }) {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Home;
|
||||||
|
|||||||
+20
-7
@@ -1,12 +1,25 @@
|
|||||||
import React, { useRef } from "react";
|
import React, { useRef } from "react";
|
||||||
import ProjectCard from "./components/ProjectCard";
|
import ProjectCard from "./components/ProjectCard";
|
||||||
import { Swiper, SwiperSlide, useSwiper } from "swiper/react";
|
import { Swiper, SwiperSlide } from "swiper/react";
|
||||||
import { Navigation, Scrollbar } from "swiper/modules";
|
import { Scrollbar } from "swiper/modules";
|
||||||
import "swiper/css";
|
import "swiper/css";
|
||||||
import "swiper/css/effect-cards";
|
import "swiper/css/effect-cards";
|
||||||
import "swiper/css/scrollbar";
|
import "swiper/css/scrollbar";
|
||||||
|
|
||||||
export default function Projects({ cardData }) {
|
/*
|
||||||
|
* The Projects page or "slide". Implements swiperjs (see documentation at https://swiperjs.com/) to create a nested
|
||||||
|
* slider/swiper inside of the root VerticalSlider. This nested slider is horizontal, and displays Project Cards
|
||||||
|
* (see /components/ProjectCard.jsx), generated for each element of the parameter cardData.
|
||||||
|
*
|
||||||
|
* Managed by /App.jsx.
|
||||||
|
* Data by /App.jsx.
|
||||||
|
*
|
||||||
|
* @param cardData json[], each object contains the following: title, descriptionA, featureList, image, url
|
||||||
|
*
|
||||||
|
* TODO: Cursor icon when hover on the slide buttons should be a pointer, not highlighter.
|
||||||
|
*/
|
||||||
|
const Projects = ({ cardData }) => {
|
||||||
|
// Reference the nested slider instead of the root slider.
|
||||||
const projectSwiperRef = useRef(null);
|
const projectSwiperRef = useRef(null);
|
||||||
|
|
||||||
const prevSlide = () => {
|
const prevSlide = () => {
|
||||||
@@ -44,8 +57,7 @@ export default function Projects({ cardData }) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Swiper
|
<Swiper
|
||||||
modules={[Navigation, Scrollbar]}
|
modules={[Scrollbar]}
|
||||||
navigation
|
|
||||||
slidesPerView={1}
|
slidesPerView={1}
|
||||||
loop={false}
|
loop={false}
|
||||||
id="project-cards-section"
|
id="project-cards-section"
|
||||||
@@ -57,10 +69,9 @@ export default function Projects({ cardData }) {
|
|||||||
<SwiperSlide key={index}>
|
<SwiperSlide key={index}>
|
||||||
<ProjectCard
|
<ProjectCard
|
||||||
title={c.title}
|
title={c.title}
|
||||||
descriptionA={c.descriptionA}
|
description={c.description}
|
||||||
featureList={c.featureList}
|
featureList={c.featureList}
|
||||||
image={c.image}
|
image={c.image}
|
||||||
imageGallery={c.imageGallery}
|
|
||||||
url={c.url}
|
url={c.url}
|
||||||
/>
|
/>
|
||||||
</SwiperSlide>
|
</SwiperSlide>
|
||||||
@@ -71,3 +82,5 @@ export default function Projects({ cardData }) {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Projects;
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import PdfViewer from "./components/PdfViewer";
|
import PdfViewer from "./components/PdfViewer";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Page to display my Resume.
|
||||||
|
*
|
||||||
|
* TODO: Rework resume to make it one page
|
||||||
|
* TODO: Convert resume to a React component, with a link to save as PDF
|
||||||
|
*/
|
||||||
export default function Resume() {
|
export default function Resume() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
export default function Error({ errorCode, message }) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="errorPage">
|
|
||||||
<h1>{errorCode}</h1>
|
|
||||||
<p>{message}</p>
|
|
||||||
|
|
||||||
<a href="/" className="link">
|
|
||||||
<div className="button shadow">Return to joshashton.dev</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,27 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
import { faGithub } from "@fortawesome/free-brands-svg-icons";
|
|
||||||
|
|
||||||
export default function ProjectCard({
|
/*
|
||||||
|
* An individual Project Card component, generated from project data in /App.jsx. Displays an image/gif
|
||||||
|
* of the project to the left on desktop, and above on mobile, a card. This card is simple, just a header,
|
||||||
|
* some description text, a list of implemented features, and a button to navigate to the GitHub for the
|
||||||
|
* project.
|
||||||
|
*
|
||||||
|
* Managed by /Projects.jsx.
|
||||||
|
* Data by /App.jsx.
|
||||||
|
*
|
||||||
|
* @param title String
|
||||||
|
* @param description String
|
||||||
|
* @param featureList String[]
|
||||||
|
* @param image String
|
||||||
|
* @param url String
|
||||||
|
*/
|
||||||
|
const ProjectCard = ({
|
||||||
title,
|
title,
|
||||||
descriptionA,
|
description,
|
||||||
featureList,
|
featureList,
|
||||||
image,
|
image,
|
||||||
imageGallery,
|
|
||||||
url,
|
url,
|
||||||
}) {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="project-card">
|
<div className="project-card">
|
||||||
<div className="img-container">
|
<div className="img-container">
|
||||||
@@ -18,7 +30,7 @@ export default function ProjectCard({
|
|||||||
<div className="project-text-column shadow">
|
<div className="project-text-column shadow">
|
||||||
<div className="project-text">
|
<div className="project-text">
|
||||||
<h3>{title}</h3>
|
<h3>{title}</h3>
|
||||||
<p>{descriptionA}</p>
|
<p>{description}</p>
|
||||||
<ul className="feature-list">
|
<ul className="feature-list">
|
||||||
{featureList.map((item, index) => (
|
{featureList.map((item, index) => (
|
||||||
<li key={index}>{item}</li>
|
<li key={index}>{item}</li>
|
||||||
@@ -34,3 +46,5 @@ export default function ProjectCard({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default ProjectCard;
|
||||||
|
|||||||
@@ -1,14 +1,23 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
|
|
||||||
// TODO: Add state which only allows one skill card at a time to be expanded.
|
/**
|
||||||
// TODO: Ensure that the expandability of the skill card is only an option when @media max-width 790px (mobile)
|
* Skill Card component.
|
||||||
|
*
|
||||||
|
* On mobile, only one card at a time may be active. The currently active card is
|
||||||
|
* managed by the parent component, Home.jsx, via the isActive and onToggle props.
|
||||||
|
*
|
||||||
|
* @param header String, the skill card's header
|
||||||
|
* @param symbol String, a Material Icon from https://fonts.google.com/icons?icon.size=24&icon.color=%23e8eaed
|
||||||
|
* @param list String[], a list of skills
|
||||||
|
* @param isActive boolean, the parent's state tracking if this instance of a skill card is the active (open) one. Only applicable to mobile.
|
||||||
|
* @param onToggle function(), the action the parent component state takes when a skill card header is pressed. Only applicable to mobile.
|
||||||
|
*/
|
||||||
const SkillCard = ({ header, symbol, list, isActive, onToggle }) => {
|
const SkillCard = ({ header, symbol, list, isActive, onToggle }) => {
|
||||||
// FUNCTION DATA
|
const [isMobile, setIsMobile] = useState(window.innerWidth <= 900);
|
||||||
const [isMobile, setIsMobile] = useState(window.innerWidth <= 790);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
const mediaQuery = window.matchMedia("(max-width: 790px)");
|
const mediaQuery = window.matchMedia("(max-width: 900px)");
|
||||||
setIsMobile(mediaQuery.matches);
|
setIsMobile(mediaQuery.matches);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -18,6 +27,7 @@ const SkillCard = ({ header, symbol, list, isActive, onToggle }) => {
|
|||||||
return () => window.removeEventListener("resize", handleResize);
|
return () => window.removeEventListener("resize", handleResize);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Display if on desktop (!isMobile) or if Home.jsx determines this to be the currently active component.
|
||||||
return (
|
return (
|
||||||
<div className="card shadow">
|
<div className="card shadow">
|
||||||
<div className="skill-card-header" onClick={() => onToggle()}>
|
<div className="skill-card-header" onClick={() => onToggle()}>
|
||||||
|
|||||||
@@ -1,11 +1,21 @@
|
|||||||
import React, { useRef, useState } from "react";
|
import React from "react";
|
||||||
import { Swiper, SwiperSlide } from "swiper/react";
|
import { Swiper, SwiperSlide } from "swiper/react";
|
||||||
import { Mousewheel, Pagination } from "swiper/modules";
|
import { Mousewheel, Pagination } from "swiper/modules";
|
||||||
|
|
||||||
import "swiper/css";
|
import "swiper/css";
|
||||||
import "swiper/css/pagination";
|
import "swiper/css/pagination";
|
||||||
|
|
||||||
export default function VerticalSlider({ slides }) {
|
/*
|
||||||
|
* For my portfolio, this is the wrapper in which all other content is displayed. Every component is
|
||||||
|
* a child of the VerticalSlider, excluding the Resume page (for now?).
|
||||||
|
*
|
||||||
|
* Utilizes swiperjs, see https://swiperjs.com/ for documentation.
|
||||||
|
*
|
||||||
|
* @param slides ReactComponent[], an array of pages or "slides" to display in a vertical slider.
|
||||||
|
*/
|
||||||
|
const VerticalSlider = ({ slides }) => {
|
||||||
|
// Mousewheel is for scrolling effect. Pagination is for "bullets" to indicate active and inactive slides. Pagination
|
||||||
|
// clickable allows for going to the corresponding page or "slide".
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Swiper
|
<Swiper
|
||||||
@@ -30,3 +40,5 @@ export default function VerticalSlider({ slides }) {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default VerticalSlider;
|
||||||
|
|||||||
+4
-3
@@ -94,7 +94,7 @@ p {
|
|||||||
|
|
||||||
.shadow {
|
.shadow {
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 13px 27px -5px hsla(240, 30.1%, 28%, 0.25),
|
0 13px 27px -5px var(--dark-0),
|
||||||
0 8px 16px -8px hsla(0, 0%, 0%, 0.3),
|
0 8px 16px -8px hsla(0, 0%, 0%, 0.3),
|
||||||
0 -6px 16px -6px hsla(0, 0%, 0%, 0.03);
|
0 -6px 16px -6px hsla(0, 0%, 0%, 0.03);
|
||||||
}
|
}
|
||||||
@@ -352,6 +352,7 @@ p {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
margin-right: 5vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tooltip container */
|
/* Tooltip container */
|
||||||
@@ -765,7 +766,7 @@ p {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 790px) {
|
@media (max-width: 900px) {
|
||||||
|
|
||||||
#primary,
|
#primary,
|
||||||
#slcc-splash {
|
#slcc-splash {
|
||||||
@@ -785,7 +786,7 @@ p {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.header .header-image {
|
.header .header-image {
|
||||||
max-height: 20vh;
|
max-height: 15vh;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
+4
-1
@@ -5,8 +5,11 @@ import App from "./App.jsx";
|
|||||||
import Resume from "./Resume.jsx";
|
import Resume from "./Resume.jsx";
|
||||||
import ErrorPage from "./ErrorPage.jsx";
|
import ErrorPage from "./ErrorPage.jsx";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Root React container and renderer. Also manages the router. Any new pages should be added here.
|
||||||
|
*/
|
||||||
const container = document.getElementById("app");
|
const container = document.getElementById("app");
|
||||||
const root = createRoot(container); // createRoot(container!) if you use TypeScript
|
const root = createRoot(container);
|
||||||
root.render(
|
root.render(
|
||||||
<Router>
|
<Router>
|
||||||
<Routes>
|
<Routes>
|
||||||
|
|||||||
Reference in New Issue
Block a user