Author SHA1 Message Date
Joders 228240ec2d added all for contact form, now test
Deploy Jody's App / build-and-deploy (push) Failing after 1m16s
2026-02-18 21:50:11 -06:00
Joders 87f0443b31 feat: testing contact form features 2026-02-18 21:34:16 -06:00
Joders 7d9c0014ed update: added link to skymoney and updated to beta status on project container
Deploy Jody's App / build-and-deploy (push) Successful in 18s
2026-02-01 00:47:39 -06:00
Joders 3caf1e30b0 updated resume, added resume data api folder for easy updates
Deploy Jody's App / build-and-deploy (push) Successful in 17s
2026-01-27 00:16:01 -06:00
Joders b4ef9fd2e0 fixed footer alignment
Deploy Jody's App / build-and-deploy (push) Successful in 19s
2026-01-21 22:38:59 -06:00
Joders 66f63523e2 fixed animation for mobile nav, and theme toggle
Deploy Jody's App / build-and-deploy (push) Successful in 15s
2026-01-07 21:18:25 -06:00
Joders 9a84d4b78c fixed animation for mobile nav
Deploy Jody's App / build-and-deploy (push) Successful in 15s
2026-01-07 21:11:42 -06:00
Joders f559a28b74 added favicon and header title; 2026-01-07 21:05:09 -06:00
Joders 4e82497640 added deploy yml
Deploy Jody's App / build-and-deploy (push) Successful in 15s
2026-01-07 21:02:20 -06:00
Joders 136c0e61db changed tab title and favicon 2026-01-07 20:39:40 -06:00
Ben Mosley 39507d4bd8 hooking up Jody with the good shit
Deploy Jody's App / build-and-deploy (push) Successful in 17s
2026-01-07 20:26:39 -06:00
Ben Mosley 0fb0877a92 hooking up jody with the good shit
Deploy Jody's App / build-and-deploy (push) Failing after 16s
2026-01-07 20:25:21 -06:00
Ben Mosley 6f7efed5e5 Hooking up Jody with the good shit
Deploy Jody's App / build-and-deploy (push) Failing after 18s
2026-01-07 20:18:47 -06:00
Joders 594aa96dc7 Update README.md 2026-01-07 05:17:26 +00:00
Joders d5a2914915 Delete variable-categories.ts 2026-01-07 05:05:08 +00:00
Joders 9181ab2fed small fix in resume 2026-01-06 20:05:45 -06:00
Joders 31b9e665b5 added about me section to navbar 2026-01-06 19:58:56 -06:00
Joders 2a56bd1b3a added about me section 2026-01-06 19:46:12 -06:00
Joders 5489adf81e fixed nav text titles, video cropping, and stack tags for miller building 2026-01-06 00:15:21 -06:00
Joders 6975f5aeab portfolio nearly complete, needs viewtesting on different moniters and screens and needs about me section, possibly files for production. 2026-01-05 23:46:21 -06:00
Joders da75479555 changed hero styling, added animations 2025-11-06 15:17:17 -06:00
Joders 261bed6e25 added fonts 2025-10-31 00:59:26 -05:00
Joders 0c4d1dd9c2 added fonts 2025-10-31 00:58:19 -05:00
Ricearoni1245 584a4325af Fix formatting in README.md for stack section 2025-10-29 02:12:42 -05:00
Ricearoni1245 fec156f6d3 Update README formatting and improve headings 2025-10-29 02:12:11 -05:00
Ricearoni1245 fb85b0d834 Fix markdown formatting in theme system section
Updated theme system section to use proper markdown formatting.
2025-10-29 02:08:32 -05:00
Joders 8f86e13dfc skeletal layout, theme functionality, mobile/desktop responsive, autoscroll 2025-10-29 02:03:46 -05:00
18 changed files with 60 additions and 240 deletions
-2
View File
@@ -1,2 +0,0 @@
VITE_CONTACT_API_URL=/api/contact
VITE_TURNSTILE_SITE_KEY=0x4AAAAAACfQya1R13nGeuOy
+3 -16
View File
@@ -7,8 +7,6 @@ on:
jobs:
build-and-deploy:
runs-on: vps-host
env:
CONTACT_HEALTH_URL: https://jodyholt.com/api/health
steps:
- name: Checkout
uses: actions/checkout@v3
@@ -46,19 +44,8 @@ jobs:
- name: Restart Contact API
run: |
set -euo pipefail
SYSTEMCTL_BIN="/usr/bin/systemctl"
if [ ! -x "$SYSTEMCTL_BIN" ]; then
SYSTEMCTL_BIN="/bin/systemctl"
fi
sudo -n "$SYSTEMCTL_BIN" restart jody-contact-api
sudo -n "$SYSTEMCTL_BIN" is-active --quiet jody-contact-api
echo "jody-contact-api service is active"
systemctl restart jody-contact-api
systemctl is-active --quiet jody-contact-api
- name: Health Check Contact API
run: |
curl --fail --show-error --silent \
--retry 8 \
--retry-delay 2 \
--retry-all-errors \
"$CONTACT_HEALTH_URL"
run: curl --fail --silent http://127.0.0.1:8787/health
+21 -29
View File
@@ -1,43 +1,35 @@
# Dependencies
node_modules/
contact-api/node_modules/
# Build artifacts
dist/
build/
contact-api/dist/
coverage/
*.tsbuildinfo
.vite/
# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Environment files (keep examples)
# Build output
dist/
build/
contact-api/dist/
# Vite cache
.vite/
# Archives and local release bundles
*.tgz
*.tar
*.tar.gz
# Tool caches
.eslintcache
.nyc_output/
.tmp/
# Editor / IDE
# VSCode settings
.vscode/
.idea/
*.swp
*.swo
*~
# Log files
*.log
# Environment variables
.env
.env.*.local
contact-api/.env
# OS generated
.DS_Store
Thumbs.db
# Optional: lock files (if you use one package manager)
# Uncomment the ones you're not using
# yarn.lock
# package-lock.json
# pnpm-lock.yamlw
-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2026 Jody Holt
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-5
View File
@@ -84,11 +84,6 @@ Create `contact-api/.env` from `contact-api/.env.example` and set your real Turn
- systemd unit template: `ops/jody-contact-api.service`
- Nginx reverse proxy snippet: `ops/nginx-contact-api.conf`
## License
This project is licensed under the MIT License.
See `LICENSE` for full text.
## Author
### Jody Holt
Binary file not shown.
-20
View File
@@ -1,20 +0,0 @@
NODE_ENV=production
PORT=8787
CONTACT_ALLOWED_ORIGIN=https://jodyholt.com,https://www.jodyholt.com
TURNSTILE_EXPECTED_HOSTNAME=jodyholt.com,www.jodyholt.com
TURNSTILE_SECRET_KEY=0x4AAAAAACfQyRwRzwsEMIfVtCSkjz7__Yc
TURNSTILE_EXPECTED_ACTION=contact_form
SMTP_HOST=mail.jodyholt.com
SMTP_PORT=587
SMTP_SECURE=false
SMTP_REQUIRE_TLS=true
SMTP_USER=portfolio-smtp
SMTP_PASS=portfolio124521!
MAIL_FROM_NAME=Portfolio Contact
MAIL_FROM_ADDRESS=contact@jodyholt.com
MAIL_TO_ADDRESS=you@jodyholt.com
MAIL_SUBJECT_PREFIX=[Portfolio Contact]
RATE_LIMIT_WINDOW_MS=600000
RATE_LIMIT_MAX=5
HONEYPOT_FIELD=website
MIN_SUBMIT_TIME_MS=3000
+1 -3
View File
@@ -10,9 +10,7 @@ const boolFromEnv = z
const envSchema = z.object({
NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
PORT: z.coerce.number().int().positive().default(8787),
// Comma-separated list of allowed browser origins, e.g.
// https://jodyholt.com,https://www.jodyholt.com
CONTACT_ALLOWED_ORIGIN: z.string().min(1),
CONTACT_ALLOWED_ORIGIN: z.string().url(),
TURNSTILE_SECRET_KEY: z.string().min(1),
TURNSTILE_EXPECTED_HOSTNAME: z.string().min(1),
TURNSTILE_EXPECTED_ACTION: z.string().min(1).default("contact_form"),
+1 -26
View File
@@ -16,25 +16,6 @@ type ApiErrorResponse = {
const app = express();
app.set("trust proxy", 1);
const normalizeOrigin = (value: string): string => {
const cleaned = value
.trim()
.replace(/^['"]|['"]$/g, "")
.replace(/\/+$/g, "");
// Some clients can emit explicit default ports. URL.origin normalizes them.
try {
return new URL(cleaned).origin.toLowerCase();
} catch {
return cleaned.toLowerCase();
}
};
const allowedOrigins = config.CONTACT_ALLOWED_ORIGIN
.split(",")
.map(normalizeOrigin)
.filter((value, index, all) => value.length > 0 && all.indexOf(value) === index);
app.use(
pinoHttp({
level: config.NODE_ENV === "production" ? "info" : "debug",
@@ -49,13 +30,7 @@ app.use(helmet());
app.use(
cors({
origin(origin, callback) {
if (!origin) {
callback(null, true);
return;
}
const normalizedOrigin = normalizeOrigin(origin);
if (allowedOrigins.includes(normalizedOrigin)) {
if (!origin || origin === config.CONTACT_ALLOWED_ORIGIN) {
callback(null, true);
return;
}
+1 -13
View File
@@ -7,18 +7,6 @@ type TurnstileVerifyResponse = {
"error-codes"?: string[];
};
const normalizeHostname = (value: string): string =>
value
.trim()
.replace(/^['"]|['"]$/g, "")
.replace(/\.+$/g, "")
.toLowerCase();
const expectedHostnames = config.TURNSTILE_EXPECTED_HOSTNAME
.split(",")
.map(normalizeHostname)
.filter((value, index, all) => value.length > 0 && all.indexOf(value) === index);
export async function verifyTurnstileToken(
token: string,
remoteIp?: string,
@@ -48,7 +36,7 @@ export async function verifyTurnstileToken(
return { ok: false, reason: codes };
}
if (!result.hostname || !expectedHostnames.includes(normalizeHostname(result.hostname))) {
if (result.hostname !== config.TURNSTILE_EXPECTED_HOSTNAME) {
return { ok: false, reason: "hostname_mismatch" };
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.
Binary file not shown.
+3 -1
View File
@@ -297,7 +297,9 @@ export function Contact() {
<div className="space-y-2">
<div ref={turnstileContainerRef} className="min-h-[66px]" />
{!TURNSTILE_SITE_KEY && (
<p className="text-xs text-contrast">Set `VITE_TURNSTILE_SITE_KEY` to enable submissions.</p>
)}
{TURNSTILE_SITE_KEY && !turnstileReady && (
<p className="text-xs text-text/65">Loading human verification...</p>
)}
+1 -34
View File
@@ -7,10 +7,7 @@ import skymoneyvideo from "../assets/video/Skymoney-video.mp4";
import skymoneyvideoMobile from "../assets/video/Skymoney-mobile-video.mp4";
import millervideo from "../assets/video/500nmain-video.mp4";
import millervideoMobile from "../assets/video/500nmain-mobile-video.mp4";
import themeTesterCover from "../assets/img/theme_tester_img.png";
import themeTesterCoverMobile from "../assets/img/theme_tester_mobile_img.png";
import themeTesterVideo from "../assets/video/themeTesterVideo.mp4";
import themeTesterVideoMobile from "../assets/video/themeTesterVideoMobile.mp4";
type Project = {
id: string;
title: string;
@@ -22,7 +19,6 @@ type Project = {
techStack: string[];
liveUrl?: string;
beta?: boolean;
inDev?: boolean;
note?: string;
comingSoon?: boolean;
};
@@ -40,7 +36,6 @@ const projects: Project[] = [
techStack: ["React", "TypeScript", "Node.js", "PostgreSQL"],
liveUrl: "https://skymoneybudget.com",
beta: true,
inDev: true,
note: "Contact Jody for beta access.",
},
{
@@ -55,22 +50,6 @@ const projects: Project[] = [
techStack: ["HTML", "CSS", "JQuery"],
liveUrl: "https://500nmain806.com",
},
{
id: "color-theme-tester",
title: "Color Theme UI Tester",
description:
"A UI Color Theme Tester that visualizes color schemes, fonts, and images on a mock UI.",
coverImage: themeTesterCover,
coverImageMobile: themeTesterCoverMobile,
video: themeTesterVideo,
videoMobile: themeTesterVideoMobile,
techStack: ["React", "Tailwind", "Typescript"],
liveUrl: "https://github.com/Ricearoni1245/Color-Theme-UI-Tester",
beta: false,
inDev: true,
note: "Stay tuned for progress!"
}
];
function VideoModal({
@@ -184,14 +163,7 @@ function ProjectCard({
<div className="absolute top-3 right-3 px-3 py-1 rounded-full bg-primary text-white text-xs font-bold uppercase tracking-wider">
Beta
</div>
)}
{project.inDev && (
<div className="absolute top-3 left-3 px-3 py-1 rounded-full bg-tertiary text-white text-xs font-bold uppercase tracking-wider">
In Progress
</div>
)
}
</div>
{/* Cover Image - Mobile */}
@@ -225,11 +197,6 @@ function ProjectCard({
Beta
</div>
)}
{project.inDev && (
<div className="absolute top-3 left-3 px-3 py-1 rounded-full bg-tertiary text-white text-xs font-bold uppercase tracking-wider">
In Progress
</div>
)}
</div>
{/* Content */}
-24
View File
@@ -9,22 +9,6 @@ export function Resume() {
<h2 className="text-4xl md:text-5xl font-extrabold font-title text-text mb-3">Resume</h2>
<div className="flex flex-wrap justify-center gap-3 text-sm text-text/70">
<span>{contactInfo.location}</span>
{contactInfo.phone && (
<>
<span className="hidden sm:inline text-primary"></span>
<a href={`tel:${contactInfo.phone.replace(/\D/g, "")}`} className="hover:text-primary anim-base">
{contactInfo.phone}
</a>
</>
)}
{contactInfo.email && (
<>
<span className="hidden sm:inline text-primary"></span>
<a href={`mailto:${contactInfo.email}`} className="hover:text-primary anim-base">
{contactInfo.email}
</a>
</>
)}
{contactInfo.website && (
<>
<span className="hidden sm:inline text-primary"></span>
@@ -41,14 +25,6 @@ export function Resume() {
</a>
</>
)}
{contactInfo.github && (
<>
<span className="hidden sm:inline text-primary"></span>
<a href={contactInfo.github} className="hover:text-primary anim-base">
GitHub
</a>
</>
)}
</div>
</div>
+27 -44
View File
@@ -2,11 +2,8 @@ export type ResumeData = {
contactInfo: {
name?: string;
location: string;
phone?: string;
email?: string;
website?: string;
linkedin?: string;
github?: string;
};
summary: string;
skills: Array<{ category: string; items: string[] }>;
@@ -21,70 +18,56 @@ export const resumeData: ResumeData = {
contactInfo: {
name: "Jody Holt",
location: "Amarillo, TX",
phone: "806.654.2813",
email: "jody@jodyholt.com",
website: "https://www.jodyholt.com",
linkedin: "https://www.linkedin.com/in/jody-holt-cis",
github: "https://github.com/Ricearoni1245",
},
summary:
"Detail-oriented full-stack software developer with experience in building secure, responsive, and maintainable full-stack applications using React, TypeScript, Node.js, Express, ASP.NET Core, C#, SQL, Prisma, and Docker. Skilled in REST API development, database schema design, secure authentication patterns, reusable UI architecture, modular code design, and enterprise application development using IIS, Windows Server, and Active Directory Integration. Known for learning new technologies quickly, gathering requirements from stakeholders or product owners, modernizing legacy systems, and delivering reliable solutions that align with user and business needs.",
"Detail-oriented software developer with experience building full-stack applications using React, TypeScript, SQL, Express, and Docker. Skilled in responsive UI, modular API design, and writing scalable code. Strong communicator known for learning new technologies quickly and solving problems efficiently.",
skills: [
{ category: "Languages", items: ["TypeScript", "JavaScript", "Python", "C#", "SQL"] },
{ category: "Front End", items: ["React", "CSS (Flexbox/Grid)", "Responsive Design", "Component Architecture", "Vite"] },
{ category: "Back End", items: ["ASP.NET Core", "Node.js", "Express.js", "REST APIs", "JWT Authentication", "Role-based Authorization", "Input Validation", "Error Handling", "Business Logic Development"] },
{ category: "Database", items: ["PostgreSQL", "Prisma ORM", "CRUD", "Query Optimization", "Data Modeling", "Relational Database Design"] },
{ category: "Software Engineering", items: ["Agile/Scrum", "Requirements Gathering", "Object-Oriented Analysis and Design", "Stakeholder Collaboration", "Modular Code Design", "Maintainable Code"] },
{ category: "Enterprise Systems", items: ["Windows Server", "IIS Application Deployment", "Active Directory Authentication", "Windows Integrated Authentication"] },
{ category: "Tools", items: ["Git/GitHub/GitLab", "Docker", "Docker Compose", "Postman"] },
{ category: "Front End", items: ["React", "TypeScript", "JavaScript", "Responsive Design", "Component Architecture", "Vite"] },
{ category: "Back End", items: ["Node.js", "Express", "REST APIs", "Authentication", "Input Validation", "JWT"] },
{ category: "Database", items: ["SQL", "Prisma", "CRUD Operations", "Query Optimization", "Database Schema Design", "Data Modeling"] },
{ category: "Tools", items: ["Git/GitHub", "Docker", "Docker Compose", "Postman"] },
],
certifications: [
"Meta Front-End Developer Certificate",
"Meta Data Engineering Certificate",
"AI Software Engineering Certificate",
"Meta Front-End Developer Certificate (Coursera)",
"Meta Data Engineering Certificate (Coursera)",
],
projects: [
{
name: "SkyMoney Budgeting App (Beta)",
stack: "React, TypeScript, Node.js, Prisma, PostgreSQL, Docker",
stack: "React, TypeScript, Node.js, Prisma, PostgreSQL",
bullets: [
"Built a full stack budgeting platform with 17 screens, 23 reusable UI components, and 47 REST endpoints supporting income tracking, transaction management, variable spending categories, and payment plan automation.",
"Implemented core budgeting logic including auto-funding, overdue payment prioritization, partial payments, bill reconciliation, and automated financial workflow management.",
"Designed a relational PostgreSQL database schema using 7 Prisma models, ensuring user-scoped writes, structured data relationships, and secure multi-user data access patterns.",
"Containerized background workers with Docker to support production-style deployment, process isolation, and scalable backend task execution.",
"Built a full-stack budgeting platform with 17 screens, reusable UI components, and 47 REST endpoints for income, transactions, variable categories, and payment plan automation.",
"Implemented core budgeting logic including auto-funding, overdue prioritization, partial payments, and bill reconciliation workflows.",
"Designed a relational database schema using 7 Prisma models with all writes scoped to user ID.",
"Containerized the API, PostgreSQL, Caddy reverse proxy, and scheduled workers using Docker Compose for production-ready deployment.",
],
},
{
name: "Responsive Portfolio Website",
stack: "React, TypeScript, Tailwind CSS, Vite",
name: "React Portfolio Website",
stack: "React, TypeScript, Vite, Responsive UI",
bullets: [
"Developed an expandable single-page portfolio using React, TypeScript, Tailwind CSS, and Vite, with 3 primary sections and 7+ reusable components.",
"Implemented responsive UI features including custom theming, navigation, interactive page elements, and mobile-friendly layouts.",
"Organized frontend code using TypeScript, reusable component patterns, custom hooks, and maintainable UI architecture.",
"Built a single-page portfolio with 3 core sections and 7 reusable components, structured for expansion.",
"Implemented interactive UI including a 5-theme color system, navigation, and mobile responsiveness.",
"Organized code using TypeScript, reusable component patterns, and a custom theme hook.",
],
},
],
workHistory: [
{
title: "IT Applications Intern",
company: "Golden Spread Electrical Coop.",
location: "Amarillo, TX",
dates: "May 2026 - Aug 2026",
bullets: [
"Modernized a legacy internal merchandise marketplace using ASP.NET Core and C#, enabling employees to redeem company-branded items through a custom internal credit system.",
"Analyzed the existing marketplace, legacy workflows, and functional requirements to identify gaps, preserve key business functionality, and define improvements for the revamped application.",
"Researched marketplace platforms, then customized the selected solution to support internal purchasing rules, company branding, custom currency handling, and administrative workflows.",
"Configured and deployed the application in a Windows Server environment using Internet Information Services (IIS).",
"Implemented enterprise authentication using Active Directory integration and Windows Integrated Authentication to support secure internal employee access and role-based authorization.",
"Presented a working demo to supervisors, collected feedback, and iterated features to improve functionality, usability, and maintainability to align with internal operations.",
],
},
{
title: "Training Specialist",
title: "Sandwich Artist",
company: "Subway",
location: "Canyon, TX",
dates: "Sep 2024 - Jan 2026",
bullets: ["Trained new hires, communicated process updates, and supported operational improvements to strengthen team consistency, documentation accuracy, and workflow efficiency."],
dates: "Sep 2024 - Present",
bullets: ["Maintained fast and accurate customer service by completing orders in a high-volume environment."],
},
{
title: "Head Lifeguard (Seasonal)",
company: "Johnson Park Youth Center",
location: "Borger, TX",
dates: "May 2022 - Aug 2025",
bullets: ["Led safety operations by monitoring swimmers and enforcing policies during high-traffic shifts."],
},
],
education: [