This is my calling card portfolio site, showcasing some of the work I've done over the past decade. This code is live at https://natedelacruz.com/. I am a full-stack developer based in NYC.
The app is built using React with Vite as a build tool.
src/pages: Holds specific views (Contact, etc.) and view-specific components.
src/components: Holds components shared across views.
src/state: Holds global state files related to recoil state (atoms and selector definitions).
src/styles: Defines all universal styles, CSS resets, and style variables.
src/App.jsx: Controls the GET call to the firestore database to get the posts and then adds them to the global state.
src/main.jsx: The main entry point into the app, holds the <BrowserRouter />.
The only data held in the global state is the response data holding info for the posts (in the code, they're referred to as pieces). I'm using recoil as it's comparatively succinct and simple react global state management solution. All non-global state management is controlled by individual components.
/public/images: images
/public/fonts: fonts
To reference them in a component or .scss file, Vite assumes all relative URLs from the /public directory as a base:
<img src="/images/my-img.png">
.elem {
background-image: url(/images/my-img.png);
}The app was using google's Firebase platform:
Hosting is via Firebase Hosting
The database built via Firebase's Firestore NoSQL cloud database.
Images and videos are hosted using Firebase Cloud Storage.
Given the small amount of posts, and this app is not for general use, it makes sense to add posts manually to the Firestore Database. Here is the schema for a post:
{
"description": "String",
"desktopCarouselCaptions": ["String"],
"desktopCarouselUrls": ["String"],
"desktopVideoCaptions": {
"TimeStamp:Integer": "String"
},
"desktopVideoPoster": "String",
"desktopVideoUrl": "String",
"externalLink": "String",
"hasDesktop": "Boolean",
"hasMobile": "Boolean",
"hasTablet": "Boolean",
"isDesigner": "Boolean",
"isDeveloper": "Boolean",
"mobileCarouselUrls": ["String"],
"mobileVideoPoster": "String",
"mobileVideoUrl": "String",
"piecesOrder": "Integer",
"route": "String",
"subTitle": "String",
"svgFile": "String",
"title": "String"
}All animated thumbnails are self-contained -- all the logic controlling a given .svg is present in that .svg. This reduces friction when making new posts. I opted for using a combination of embedded JS and CSS animations that loop instead of SMIL animations which are more clunky to implement. When making a new thumbnail I'd make the basic thumb, with all its pieces in one illustrator file exported as an .svg, then edit that file using a text editor.
$ npm run dev: runs the app in development mode, view it at http://localhost:5173/. Hot reloading is enabled.
$ npm run build: builds the app for production in the /dist directory. View it by running $ npm run preview.
$ npm run preview: runs the built production app from the /dist directory. View it at http://localhost:4173/.
Build the app to the /dist directory:
$ npm run build
Ensure the firebase.json is pointing to the /dist directory:
{
"hosting": {
"public": "dist"
}
}Using the firebase CLI run:
$ firebase deploy --only hosting
This will push the site to production.
Secrets are placed in a .env file and have keys prefixed with VITE_, as recommended by the docs.