diff --git a/app/(API Routes)/api/blog/search/route.ts b/app/(API Routes)/api/blog/search/route.ts index 8113116..b7affa1 100644 --- a/app/(API Routes)/api/blog/search/route.ts +++ b/app/(API Routes)/api/blog/search/route.ts @@ -7,7 +7,6 @@ export function GET(request: Request) { const dataFile = path.join( process.cwd(), - "src", "app", "(API Routes)", "api", diff --git a/app/(API Routes)/api/search/[state]/route.ts b/app/(API Routes)/api/search/[state]/route.ts new file mode 100644 index 0000000..8803252 --- /dev/null +++ b/app/(API Routes)/api/search/[state]/route.ts @@ -0,0 +1 @@ +// Dynamic State resource route \ No newline at end of file diff --git a/app/(API Routes)/api/search/resources.json b/app/(API Routes)/api/search/resources.json new file mode 100644 index 0000000..e26fa05 --- /dev/null +++ b/app/(API Routes)/api/search/resources.json @@ -0,0 +1,50 @@ +[ + { + "id": 1, + "url": "https://findtreatment.gov/", + "description": "Find treatment facilities in the United States or U.S. Territories for substance abuse/addiction and/or mental health problems." + }, + { + "id": 2, + "url": "www.careeronestop.org/", + "description": "Help finding second chance employers by location" + }, + { + "id": 3, + "url": "www.hudexchange.info/", + "description": "Housing and homeless shelters by location" + }, + { + "id": 4, + "url": "www.hirefelons.org/", + "description": "Hiring resources for felons" + }, + { + "id": 5, + "url": "https://www.spl.org/programs-and-services/civics-and-social-services/resources-for-the-formerly-incarcerated", + "description": "Information about resources available to the formerly incarcerated" + }, + { + "id": 6, + "url": "www.goodwill.org/", + "description": "Goodwill has re entry jobs and training services" + }, + { "id": 7, "url": "www.salvationarmyusa.org/", "description": "Resources" }, + { "id": 8, "url": "www.foodfinder.us/", "description": "Resources" }, + { + "id": 9, + "url": "https://georeentryconnect.com/", + "description": "General resources by state" + }, + { + "id": 10, + "url": "www.honestjobs.com/", + "description": "Job seekers center for community transitions" + }, + { + "id": 11, + "url": "https://centerforcommunitytransitions.org/", + "description": "Center for Community Transitions" + }, + { "id": 12, "url": "www.indeed.com/", "description": "Job search" } +] diff --git a/app/(API Routes)/api/search/route.ts b/app/(API Routes)/api/search/route.ts new file mode 100644 index 0000000..2df45d3 --- /dev/null +++ b/app/(API Routes)/api/search/route.ts @@ -0,0 +1,8 @@ +// Catch all search route +// Import the resources file +/* +UPDATE resource file via vercel blob storage for source of truth +USING os path to dynamically read files from vercel blob storage +*/ +// +// diff --git a/app/(Pages)/about-us/page.tsx b/app/(Pages)/about/page.tsx similarity index 96% rename from app/(Pages)/about-us/page.tsx rename to app/(Pages)/about/page.tsx index b32a5e7..ac18119 100644 --- a/app/(Pages)/about-us/page.tsx +++ b/app/(Pages)/about/page.tsx @@ -1,7 +1,7 @@ "use client" import NextImage from 'next/image'; import TeamMemberCard from '../../../components/about/team-member-card'; -import { GemIcon } from '@/components/ui/icons'; +// import { GemIcon } from '@/components/ui/icons'; import { executiveBoard, teamMembers } from '../../../data/team'; import { Container, Box, Text, AbsoluteCenter, VStack, HStack, Heading, @@ -10,6 +10,7 @@ import { import { poppins } from '../../../components/ui/fonts'; import NextLink from 'next/link'; import checkDeviceSize from '../../../components/ui/breakpoints'; +import { Icon } from '@/components/ui/icons/icon'; export default function AboutUs() { @@ -58,7 +59,8 @@ export default function AboutUs() {
- + + {/* */} Vision @@ -70,7 +72,7 @@ export default function AboutUs() {
- + Mission diff --git a/app/(Pages)/contact/page.tsx b/app/(Pages)/contact/page.tsx index 5851574..03487d9 100644 --- a/app/(Pages)/contact/page.tsx +++ b/app/(Pages)/contact/page.tsx @@ -1,213 +1,207 @@ -"use client" -import Image from 'next/image'; -import ContactForm from '../../../components/contact/contact-form'; -import { contactInfo, socialLinks, mapEmbedUrl } from '../../../data/contact'; +"use client"; -import { - Container, Box, Text, AbsoluteCenter, VStack, HStack, Heading, - Link as ChakraLink, SimpleGrid, Image as ChakraImage -} from '@chakra-ui/react'; -import { poppins } from '../../../components/ui/fonts'; +import Image from "next/image"; +import ContactForm from "../../../components/contact/contact-form"; +import { contactInfo, socialLinks, mapEmbedUrl } from "../../../data/contact"; -import checkDeviceSize from '@/components/ui/breakpoints'; -import { HeaderTemplate, PageBuilder } from '../../../components/page-builder/template'; +import NextLink from "next/link"; +import { + Box, + Text, + HStack, + Heading, + Link as ChakraLink, + Image as ChakraImage, +} from "@chakra-ui/react"; + +import checkDeviceSize from "@/components/ui/breakpoints"; +import { + HeaderTemplate, + PageBuilder, +} from "@/components/page-builder/template"; +import { Icon } from "@/components/ui/icons/icon"; export default function Contact() { const notMobileDevice = checkDeviceSize(); return ( <> - - { - notMobileDevice ? ( - - - - - - -
- {/* Get In Touch Section */} -
-
-

Get in Touch

-

- We're here to support you. Reach out to learn more about our services, volunteer opportunities, and ways to contribute -

-
- {socialLinks.map((link) => ( - - {link.icon} - {link.platform} - - ))} -
-
-
- - {/* Contact Info Cards Section */} -
-
- {contactInfo.map((info) => { - const content = info.href ? ( - - {info.value} - - ) : ( -

{info.value}

- ); - - return ( -
-
- {info.icon} -
-

{info.label}

- {content} -
- ); - })} -
-
- - {/* Google Maps Embed Section */} -
-
- + {/* Contact Form Section */}
-
- -
+
-
- -
- ) : ( - <> - - -
- {/* Hero Section */} -
+
+ + +
+ + ) : ( + <> +
+ {/* Hero Section */} +
+ Contact Mokse + +
+

Contact Us

+

+ Find out how you can contribute and make a positive impact in + your community +

+
+
+ + {/* Get In Touch Section */} +
+
+

Get in Touch

+

+ We're here to support you. Reach out to learn more about + our services, volunteer opportunities, and ways to contribute +

-

Contact Us

-

Find out how you can contribute and make a positive impact in your community

-
-
- - {/* Get In Touch Section */} -
-
-

Get in Touch

-

- We're here to support you. Reach out to learn more about our services, volunteer opportunities, and ways to contribute -

-
- {socialLinks.map((link) => ( - - {link.icon} - {link.platform} - - ))} -
-
-
- - {/* Contact Info Cards Section */} -
-
- {contactInfo.map((info) => { - const content = info.href ? ( - - {info.value} - - ) : ( -

{info.value}

- ); - - return ( -
-
- {info.icon} -
-

{info.label}

- {content} -
- ); - })} + {socialLinks.map((link) => ( + + + {link.platform} + + ))}
-
- - {/* Google Maps Embed Section */} -
-
- - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - + + + + - - - + + + + - - - + + + + - - - - - - - ) : ( - - + + + + + + + + + ) : ( + + + - - - - Empowering Change Through Education And Advocacy - -

We strive to break down barriers and stop the stigma associated with - incarceration through consulting services, educational conferences, and business support programs.

- -
-
-
- - - - A Commitment to Empowerment + + + + Empowering Change Through Education And Advocacy + - To transform the lives of learners by providing accessible education, entrepreneurial - support, and empowerment resources that foster personal and professional growth. - - - - - - -
- {empowerment.map((f) => ( - - ))} -
-
- {/* - College students -

Make a Difference – Get Involved!

-

Are you passionate about helping justice-impacted inBoxiduals? - We need compassionate volunteers to assist in various roles, including - mentorship, tutoring, and administrative support.

- -
- - - {getInvolved.map((f) => ( - - ))} - - - - -

special story: The Three Words That Change My Life.

-

Mokse is proud to share the TEDx talk of Dr. Matthews.

- - - -
+

+ We strive to break down barriers and stop the stigma + associated with incarceration through consulting services, + educational conferences, and business support programs. +

+ +
+ + + - - + + A Commitment to Empowerment + + + To transform the lives of learners by providing accessible + education, entrepreneurial support, and empowerment resources + that foster personal and professional growth. + + + + + + - - - - - - */} - - ) - } - +
+ {empowerment.map((f) => ( + + ))} +
+
+ + )} ); } diff --git a/components/common/footer.tsx b/components/common/footer.tsx index f2289d0..aeac678 100644 --- a/components/common/footer.tsx +++ b/components/common/footer.tsx @@ -13,25 +13,16 @@ import { Heading, Center, IconButton, - Icon, Image, Box, Flex, } from "@chakra-ui/react"; -import { useColorMode } from "../ui/color-mode"; +import { useColorMode, useColorModeValue } from "../ui/color-mode"; import { poppins } from "../ui/fonts"; import checkDeviceSize from "../ui/breakpoints"; -import { - FB, - LinkedIn, - YouTube, - Instagram, - PhoneIcon, - MapPinIcon, - MailIcon, - AngleRightIcon, -} from "../ui/icons"; + +import { Icon } from "@/components/ui/icons/icon"; export default function Footer() { const { colorMode } = useColorMode(); @@ -54,11 +45,10 @@ export default function Footer() { - + @@ -104,7 +94,7 @@ export default function Footer() { target="_blank" rel="noopener noreferrer" > - + @@ -124,7 +114,7 @@ export default function Footer() { target="_blank" rel="noopener noreferrer" > - + @@ -144,7 +134,7 @@ export default function Footer() { target="_blank" rel="noopener noreferrer" > - + @@ -164,7 +154,7 @@ export default function Footer() { target="_blank" rel="noopener noreferrer" > - + @@ -187,10 +177,10 @@ export default function Footer() {
- Home
@@ -200,10 +190,10 @@ export default function Footer() {
- About Us
@@ -213,10 +203,10 @@ export default function Footer() {
- Services
@@ -226,10 +216,10 @@ export default function Footer() {
- Contact
@@ -303,7 +293,12 @@ export default function Footer() { {/* test */} - + 497 Hooksett Road, Suite 362, @@ -313,7 +308,12 @@ export default function Footer() { - + {/* test */} @@ -326,7 +326,7 @@ export default function Footer() { - + {/* */} diff --git a/components/common/iconbutton.tsx b/components/common/iconbutton.tsx new file mode 100644 index 0000000..0b5cb6a --- /dev/null +++ b/components/common/iconbutton.tsx @@ -0,0 +1,25 @@ +// Create a reusable IconButton component that takes in link, platform, url, and icon as props and renders an anchor tag with the provided icon. +// & React.ComponentProps + +export default function IconButton({ + link, + platform, + url, + icon, +}: { + link: string; + platform: string; + url: string; + icon: React.ReactNode; +}) { + return ( + + {icon} + + ); +} diff --git a/components/common/navbar.tsx b/components/common/navbar.tsx index 78ee2f8..efca854 100644 --- a/components/common/navbar.tsx +++ b/components/common/navbar.tsx @@ -1,141 +1,161 @@ -'use client'; +"use client"; -import Image from 'next/image'; -import NextLink from 'next/link'; -import { HStack, Container, Link as ChakraLink, Button, Text } from '@chakra-ui/react'; -import { MdBrightness4 as MdMoon, MdBrightness5 as MdSun } from 'react-icons/md'; -import { useColorMode } from '../ui/color-mode'; -import { useEffect, useState } from 'react'; -import { openSans } from '../ui/fonts'; -import useDeviceSize from '../ui/breakpoints'; +import Image from "next/image"; +import NextLink from "next/link"; +import { + HStack, + Container, + Link as ChakraLink, + Button, + Text, +} from "@chakra-ui/react"; +import { + MdBrightness4 as MdMoon, + MdBrightness5 as MdSun, +} from "react-icons/md"; +import { useColorMode } from "../ui/color-mode"; +import { useEffect, useState } from "react"; +import { openSans } from "../ui/fonts"; +import useDeviceSize from "../ui/breakpoints"; export default function Navbar() { - const { colorMode, toggleColorMode } = useColorMode(); - const deviceSize = useDeviceSize(); - const notMobileDevice = - deviceSize !== 'base' - && - deviceSize !== 'sm'; + const { colorMode, toggleColorMode } = useColorMode(); + const deviceSize = useDeviceSize(); + const notMobileDevice = deviceSize !== "base" && deviceSize !== "sm"; - const [isMounted, setIsMounted] = useState(false); - const [isFixed, setIsFixed] = useState(false); + const [isMounted, setIsMounted] = useState(false); + const [isFixed, setIsFixed] = useState(false); - // Mount check for screen size check - useEffect(() => { - setIsMounted(true); - }, []); + // Mount check for screen size check + useEffect(() => { + setIsMounted(true); + }, []); - // Check device scroll position for navbar position - useEffect(() => { - if (!isMounted) return; - const handleScroll = () => { - const scrollPosition = window?.scrollY; - setIsFixed(scrollPosition >= 50); - }; - handleScroll(); - window?.addEventListener('scroll', handleScroll); - return () => window?.removeEventListener('scroll', handleScroll); - }, [isMounted]); + // Check device scroll position for navbar position + useEffect(() => { + if (!isMounted) return; + const handleScroll = () => { + const scrollPosition = window?.scrollY; + setIsFixed(scrollPosition >= 50); + }; + handleScroll(); + window?.addEventListener("scroll", handleScroll); + return () => window?.removeEventListener("scroll", handleScroll); + }, [isMounted]); - // Container props - const ContainerProps = { - h: notMobileDevice ? ('10vh') : ('5vh'), - zIndex: 1, - position: isFixed ? ('fixed') : ('absolute'), - fluid: true, - py: - isFixed ? - notMobileDevice ? (8) : (2) : - notMobileDevice ? (8) : (5), - transition: "all 0.2s ease-in-out", - transform: - isFixed ? ('translateY(0)') : ('translateY(-20px)'), - opacity: isFixed ? 1 : 0.9, - boxShadow: isFixed ? '2xl' : 'none', - bg: - isFixed ? - colorMode === "light" ? 'black' - : 'blackAlpha.950' - : 'transparent', - className: openSans.className, - } as const; + // Container props + const ContainerProps = { + h: notMobileDevice ? "10vh" : "5vh", + zIndex: 1, + position: isFixed ? "fixed" : "absolute", + fluid: true, + py: isFixed ? (notMobileDevice ? 8 : 2) : notMobileDevice ? 8 : 5, + transition: "all 0.2s ease-in-out", + transform: isFixed ? "translateY(0)" : "translateY(-20px)", + opacity: isFixed ? 1 : 0.9, + boxShadow: isFixed ? "2xl" : "none", + bg: isFixed + ? colorMode === "light" + ? "black" + : "blackAlpha.950" + : "transparent", + className: openSans.className, + } as const; - // Nav text props - const navTextProps = { - fontSize: '24px', - fontWeight: 600, - _light: { color: 'white' }, - } as const; + // Nav text props + const navTextProps = { + fontSize: "16px", + fontWeight: 600, + _light: { color: "white" }, + } as const; - return ( - - ); + return ( + + ); } diff --git a/components/contact/contact-form.tsx b/components/contact/contact-form.tsx index b514688..6a87fb7 100644 --- a/components/contact/contact-form.tsx +++ b/components/contact/contact-form.tsx @@ -1,6 +1,18 @@ -'use client'; +"use client"; -import { useState, FormEvent } from 'react'; +import { + Box, + Button, + Card, + Container, + Field, + Fieldset, + Input, + Stack, + Textarea, +} from "@chakra-ui/react"; +import { useState, FormEvent } from "react"; +import { toaster, Toaster } from "../ui/toaster"; interface FormData { name: string; @@ -10,30 +22,63 @@ interface FormData { } interface SubmissionStatus { - type: 'success' | 'error' | null; + type: "success" | "error" | null; message: string; } export default function ContactForm() { const [formData, setFormData] = useState({ - name: '', - email: '', - subject: '', - message: '', + name: "", + email: "", + subject: "", + message: "", }); const [isSubmitting, setIsSubmitting] = useState(false); - const [status, setStatus] = useState({ type: null, message: '' }); + const submitToasterId = "contact-form-submission"; + const [showToast, setShowToast] = useState(false); + const [toastMessage, setToastMessage] = useState({ + error: () => "", + loading: () => + toaster.loading({ + id: submitToasterId, + title: "Submitting form", + description: "Please wait while we submit your message.", + type: "info", + closable: true, + }), + update: () => "", + updateSuccess: () => + toaster.update(submitToasterId, { + title: "Form submitted", + description: "Your message has been sent successfully.", + type: "success", + duration: 3000, + closable: true, + }), + reset: () => + toaster.create({ + title: "Form reset", + description: "The contact form has been cleared.", + type: "info", + closable: true, + }), + }); + const [status, setStatus] = useState({ + type: null, + message: "", + }); const handleSubmit = async (e: FormEvent) => { e.preventDefault(); setIsSubmitting(true); - setStatus({ type: null, message: '' }); + setStatus({ type: null, message: "" }); + if (toaster.isVisible(submitToasterId)) return toastMessage.loading(); try { - const response = await fetch('@/app/(API Routes)/api/contact/route', { - method: 'POST', + const response = await fetch("/api/contact", { + method: "POST", headers: { - 'Content-Type': 'application/json', + "Content-Type": "application/json", }, body: JSON.stringify(formData), }); @@ -42,101 +87,131 @@ export default function ContactForm() { if (response.ok) { setStatus({ - type: 'success', - message: 'Thank you for your message! We will get back to you soon.', + type: "success", + message: "Thank you for your message! We will get back to you soon.", }); setFormData({ - name: '', - email: '', - subject: '', - message: '', + name: "", + email: "", + subject: "", + message: "", }); } else { setStatus({ - type: 'error', - message: data.error || 'Something went wrong. Please try again.', + type: "error", + message: data.error || "Something went wrong. Please try again.", }); } } catch { setStatus({ - type: 'error', - message: 'Failed to send message. Please try again later.', + type: "error", + message: "Failed to send message. Please try again later.", }); } finally { setIsSubmitting(false); + setTimeout(() => { + toastMessage.updateSuccess(); + }, 3000); } }; return ( -
-
- - setFormData({ ...formData, name: e.target.value })} - disabled={isSubmitting} - /> -
- -
- - setFormData({ ...formData, email: e.target.value })} - disabled={isSubmitting} - /> -
- -
- - setFormData({ ...formData, subject: e.target.value })} - disabled={isSubmitting} - /> -
- -
- -