Skip to content

Commit

Permalink
feat: landing page and cart-item storage
Browse files Browse the repository at this point in the history
  • Loading branch information
spencerlepine committed Oct 19, 2024
1 parent c4a6044 commit 5294447
Show file tree
Hide file tree
Showing 101 changed files with 2,563 additions and 419 deletions.
1 change: 1 addition & 0 deletions .github/status-maintained-badge.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/swagsticker.com-screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.0.3] - 2024-10-14

- Client-side cart item storage
- Catalog filters/pagination
- 47 real products with mockup images

## [0.0.2] - 2024-10-14

MVP landing page and product catalog

## [0.0.1] - 2024-09-18

Boilerplate Next.js (App Router) code
Expand Down
674 changes: 674 additions & 0 deletions COPYING

Large diffs are not rendered by default.

92 changes: 78 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,101 @@
# Next.js Fullstack DigitalOcean Droplet VM
# SwagSticker.com ![Repo Status Badge](./.github/status-maintained-badge.svg)

Fullstack Next.js (App Router) deployed to DigitalOcean App Platform
E-commerce store for developer laptop stickers, fully autonomous and scale-abe. Built with Jamstack and server-side rendering.

## Local Development
<img width="800px" style="margin:auto" src="./.github/swagsticker.com-screenshot.png" alt="SwagSticker.com website screenshot">

### Prerequisites
## 🎥 Demo Video

https://github.com/jadiaheno/vention-machine-cloud-test/assets/13062328/a42d55bb-0381-4d16-9ea3-24aced02e5d3

## 🎯 Project Overview

<!-- TODO_README -->

- designed autonomous and scalable e-commerce store
- enabled both guest checkout and no-password login for seamless checkout experience (JSON-Web-Token (JWT))
- designed an accessible, responsive, and performant UI with Next.js and TailwindCSS
- utilized open-source SDKs to integrate third-party APIs
- reduced initial load time to 1.2secs
- generated product images with OpenCV python script
- optimized load times and SEO with server-side rendering
- avoided complex database setup with JSON product catalog and GraphQL for easy migration to headless CMS
- secured checkout payments with stripe forms and bot detection
- load test, >95% success rate with <300ms response for up to XXX users peak traffic

## 🛠️ Built With

- **Framework**: Next.js (React), TailwindCSS
- **Language**: TypeScript
- **Hosting**: Vercel (w/ Amazon Route53 domain)
- **Third-Party**: Stripe (payments), Printify (drop-shipping)

> **Specifications:**
>
> - Node.js: >=v20
> - React.js: v18
> - Next.js: v14 (App Router)
> - Vercel: serverless, hobby plan
## 🏗️ System Diagram

<!-- TODO_README -->

![SwagSticker.com system diagram](./.github/swagsticker.com-system-diagram.png)

## ⭐️ Features

<!-- TODO_README -->

- <auth feature no pass, or stay guest>
- <browse the catalog, search, filter>
- <add items to cart, proceed to checkout>
- <use secure stripe form, place order>
- <track shipping status>

## 💻 Local Development

📌 Note: this demo can be run locally; the production code is private for security reasons

#### Prerequisites

- **Node.js** installed on your machine (download [here](https://nodejs.org/en/download))
- Stripe developer account (+ API keys)
- Printify developer account (+ API keys)

### Installation
#### Installation

```sh
git clone https://github.com/spencerlepine/nextjs-digitalocean-poc
cd nextjs-digitalocean-poc
git clone https://github.com/spencerlepine/swagsticker.com
cd swagsticker.com
cp .env.template .env.development
npm install
```

### Run Locally
#### Run Locally

```sh
cp .env.template .env.local
npm run dev
# visit http://locahost:3000
```

### Production Build
#### Production Build

```sh
cp .env.template .env.production
NODE_ENV=production npm run build
npm run build
```

## Resources
#### Local Docker Container

```sh
cp .env.template .env.development
docker-compose -f ./docker/development docker-compose.yml up -d
# visit http://locahost:3001
```

## License

GNU General Public License v3.0 or later

- Next.js docs: https://nextjs.org/docs/getting-started/installation
- Auth0 walkthrough: https://auth0.com/docs/quickstart/webapp/nextjs/interactive
See [COPYING](COPYING) to see the full text.
19 changes: 1 addition & 18 deletions next.config.mjs
Original file line number Diff line number Diff line change
@@ -1,21 +1,4 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'lh3.googleusercontent.com',
port: '',
pathname: '/**',
},
{
protocol: 'https',
hostname: 's.gravatar.com',
port: '',
pathname: '/**',
},
],
}
};
const nextConfig = {};

export default nextConfig;
25 changes: 19 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,24 +1,33 @@
{
"name": "swagsticker.com-demo",
"author": "Spencer Lepine @spencerlepine",
"description": "Ecommerce store, a fullstack Next.js (App Router) application",
"description": "E-commerce store, a fullstack Next.js (App Router) application",
"version": "0.0.0",
"private": true,
"engines": {
"node": ">=18.17.0"
},
"browserslist": [
"chrome 64",
"edge 79",
"firefox 67",
"opera 51",
"safari 12"
],
"scripts": {
"dev": "next dev",
"build": "next build",
"build": "NODE_ENV=production next build",
"test": "echo 'No tests added yet' && exit 0",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@auth0/nextjs-auth0": "^3.5.0",
"fuse.js": "^7.0.0",
"next": "14.2.11",
"react": "^18",
"react-dom": "^18"
"react-dom": "^18",
"stripe": "^16.9.0",
"use-shopping-cart": "^3.2.0"
},
"devDependencies": {
"@types/node": "^20",
Expand All @@ -36,8 +45,12 @@
"eslintConfig": {
"extends": [
"next/core-web-vitals",
"next/typescript"
]
"next/typescript",
"plugin:jsx-a11y/recommended"
],
"rules": {
"@typescript-eslint/ban-ts-comment": "warn"
}
},
"prettier": {
"printWidth": 180,
Expand Down
Binary file added public/images/2x2in-sticker-mockup.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/3x3in-sticker-mockup.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/4x4in-sticker-mockup.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/airtable.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/amazon-web-services-hexagon.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/amazon-web-services.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/android.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/ansible.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/azure.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/c-plus-plus.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/c-sharp.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/c.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/chatgpt.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/chrome.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/cloudflare.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/css.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/django.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/docker.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/dot-net-core.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/elasticsearch.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/figma.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/firebase.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/flask.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/git.jpg
Binary file added public/images/github-head.jpg
Binary file added public/images/github-octocat.jpg
Binary file added public/images/go-gopher.jpg
Binary file added public/images/graphql.jpg
Binary file added public/images/html.jpg
Binary file added public/images/java.jpg
Binary file added public/images/javascript.jpg
Binary file added public/images/jquery.jpg
Binary file added public/images/kubernetes.jpg
Binary file added public/images/mongo-db-shield.jpg
Binary file added public/images/nodejs.jpg
Binary file added public/images/open-source.jpg
Binary file added public/images/python.jpg
Binary file added public/images/react.jpg
Binary file added public/images/ruby-on-rails.jpg
Binary file added public/images/ruby.jpg
Binary file added public/images/rust.jpg
Binary file added public/images/slack.jpg
Binary file added public/images/stack-overflow.jpg
Binary file added public/images/supabase.jpg
Binary file added public/images/svelte.jpg
Binary file added public/images/tensorflow.jpg
Binary file added public/images/typescript.jpg
Binary file added public/images/ubuntu.jpg
Binary file added public/images/vscode.jpg
Binary file added public/images/webpack.jpg
1 change: 0 additions & 1 deletion public/next.svg
Diff not rendered.
8 changes: 8 additions & 0 deletions public/sitemap.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<xml version="1.0" encoding="UTF-8">
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>http://www.swagsticker.com</loc>
<lastmod>2024-10-14</lastmod>
</url>
</urlset>
</xml>
37 changes: 37 additions & 0 deletions src/app/(generic)/contact/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
export default function ContactForm() {
return (
<div className="my-8 mx-20">
<section className="container mx-auto px-4 py-8">
<h2 className="text-3xl font-bold mb-4">Contact Us</h2>
<p className="text-gray-700 mb-4">For general inquiries or customer support, please email us at:</p>
<a href="mailto:[email protected]" className="text-blue-500 hover:underline">
[email protected]
</a>
<h3 className="text-2xl font-semibold mt-6 mb-2">Contact Form</h3>
<form action="https://public.herotofu.com/v1/aae45b10-27cb-11ed-9d54-c9f9d2b00e7b" method="post" acceptCharset="UTF-8" className="space-y-4">
<div>
<input name="Name" placeholder="Full name" id="name" type="text" required className="border rounded-md px-4 py-2 w-full" data-testid="contact-form-name-input" />
</div>
<div>
<input name="Email" placeholder="Your email" id="email" type="email" required className="border rounded-md px-4 py-2 w-full" data-testid="contact-form-email-input" />
</div>
<div>
<textarea
placeholder="Your message goes here..."
data-testid="contact-form-message-input"
name="message"
required
className="border rounded-md px-4 py-2 w-full h-24"
></textarea>
</div>
<div>
<input type="submit" value="Send Message" className="bg-blue-500 text-white font-bold py-2 px-4 rounded-md hover:bg-blue-600" />
<div aria-hidden="true">
<input type="text" name="_gotcha" tabIndex={-1} autoComplete="off" />
</div>
</div>
</form>
</section>
</div>
);
}
62 changes: 62 additions & 0 deletions src/app/(generic)/faq/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
const faqs = [
{
question: 'How long do items take to produce/ship?',
answer: 'Items are typically produced and shipped within 4-8 business days.',
},
{
question: 'What is your return policy?',
answer:
'While we do not accept returns, we understand that sometimes things don&apos;t go as planned. If you&apos;re dissatisfied with your purchase, please contact our customer support team to discuss a possible refund. We&apos;ll do our best to find a solution that works for you.',
},
{
question: 'What payment methods do you accept?',
answer: 'We accept all major credit cards (Visa, Mastercard, American Express, Discover), debit cards, and digital wallets like Apple Pay and Google Pay.',
},
{
question: 'Do you offer international shipping?',
answer: 'No, we currently only offer shipping within the United States.',
},
// {
// question: 'Do you offer international shipping?',
// answer:
// 'Yes, we offer international shipping to most countries with estimated delivery times of 2-5 business days for US orders and 10-30 business days for international orders. Please enter your shipping address during checkout for accurate rates and times.',
// },
{
question: 'How can I track my order?',
answer:
"Once your order has shipped, you will receive a tracking number via email. You can use this number to track the status of your shipment on the shipping carrier's website. You can also find your tracking number in your order details on your account page.",
},
{
question: 'What if my item is damaged or defective?',
answer:
'If you receive a damaged or defective item, please contact our customer support team within 14 days of receiving your order. We will review your case and initiate a refund process if applicable. We do not accept returns for damaged or defective items.',
},
{
question: 'Can I cancel my order?',
answer:
'Unfortunately, we do not currently support order cancellations. However, we are working on adding this feature in the near future. If your order has not yet shipped, please contact our customer support team to see if there are any other options available.',
},
{
question: 'How do I contact customer support?',
answer:
'You can contact our customer support team by email at [email address]. Our customer support hours are [hours of operation]. For general inquiries or customer support, please visit our Contact page.',
},
];

export default function FAQPage() {
return (
<div className="my-8 mx-20">
<div className="container mx-auto px-4 py-8">
<h1 className="text-3xl font-bold mb-4">Frequently Asked Questions</h1>
<ul className="list-disc pl-4 mt-2 list-none">
{faqs.map((faq, index) => (
<li key={index} className="text-gray-700 mt-4">
<span className="font-semibold">{faq.question}</span>
<p>{faq.answer}</p>
</li>
))}
</ul>
</div>
</div>
);
}
30 changes: 30 additions & 0 deletions src/app/(generic)/privacy/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export default async function PrivacyPage() {
return (
<div className="my-8 mx-20">
<div className="container mx-auto px-4 py-8">
<h1 className="text-3xl font-bold mb-4">Privacy Policy</h1>
<p className="text-gray-700 leading-loose">
This Privacy Policy describes how we collect, use, and disclose your information when you use our website and the choices you have associated with that data.
</p>
<h2 className="text-2xl font-semibold mt-6 mb-2">Information We Collect</h2>
<ul className="list-disc pl-4 mt-2">
<li className="text-gray-700">Name and Email Address: We collect your name and email address when you directly provide it to us through a form on this site.</li>
<li className="text-gray-700">
Device Information: We use Google Analytics to collect information about your device, location (approximate), IP address, and usage of this site.
</li>
</ul>
<h2 className="text-2xl font-semibold mt-6 mb-2">How We Use Your Information</h2>
<p className="text-gray-700 leading-loose">We use the information we collect to:</p>
<ul className="list-disc pl-4 mt-2">
<li className="text-gray-700">Communicate with you (using your email).</li>
<li className="text-gray-700">Monitor usage and make decisions about content creation (using Google Analytics data).</li>
</ul>
<h2 className="text-2xl font-semibold mt-6 mb-2">Contact Us</h2>
<p className="text-gray-700">If you have any questions or concerns about our privacy practices, please contact us at:</p>
<a href="mailto:[email protected]" className="text-blue-500 hover:underline">
[email protected]
</a>
</div>
</div>
);
}
17 changes: 4 additions & 13 deletions src/app/account/page.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
import Image from 'next/image';
import { getSession } from '@auth0/nextjs-auth0';

// TODO_AUTH_ACCOUNT - page
export default async function AccountPage() {
const { user } = (await getSession()) || {};

return (
user && (
<div>
<Image src={user.picture} alt={user.name} width="100" height="100" />
<h2>{user.name}</h2>
<p>{user.email}</p>
<a href="/api/auth/logout">Logout</a>
</div>
)
<div>
<p>w.i.p</p>
</div>
);
}
6 changes: 0 additions & 6 deletions src/app/api/auth/[auth0]/route.ts

This file was deleted.

9 changes: 0 additions & 9 deletions src/app/api/auth/me/route.ts

This file was deleted.

Loading

0 comments on commit 5294447

Please sign in to comment.