Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Deploy to Vercel

on:
push:
branches: [ main, feature/ecommerce-complete ]
pull_request:
branches: [ main ]

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Build application
run: npm run build

- name: Deploy to Vercel
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.ORG_ID }}
vercel-project-id: ${{ secrets.PROJECT_ID }}
vercel-args: '--prod'
12 changes: 12 additions & 0 deletions env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Database
DATABASE_URL="postgresql://username:password@localhost:5432/cartzy"

# NextAuth
NEXTAUTH_URL="http://localhost:3001"
NEXTAUTH_SECRET="your-secret-key-here"

# OAuth Providers
GOOGLE_CLIENT_ID="your-google-client-id"
GOOGLE_CLIENT_SECRET="your-google-client-secret"
GITHUB_CLIENT_ID="your-github-client-id"
GITHUB_CLIENT_SECRET="your-github-client-secret"
27 changes: 18 additions & 9 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";
import js from '@eslint/js'
import { FlatCompat } from '@eslint/eslintrc'
import path from 'path'
import { fileURLToPath } from 'url'

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)

const compat = new FlatCompat({
baseDirectory: __dirname,
});
})

const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"),
];
js.configs.recommended,
...compat.extends('next/core-web-vitals'),
{
rules: {
'react-hooks/exhaustive-deps': 'warn',
'no-undef': 'off',
'no-unused-vars': 'warn',
},
},
]

export default eslintConfig;
export default eslintConfig
17 changes: 3 additions & 14 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,10 @@ import type { NextConfig } from "next";

const nextConfig: NextConfig = {
images: {
remotePatterns: [
{
protocol: "https",
hostname: "lh3.googleusercontent.com",
},
{
protocol: "https",
hostname: "avatars.githubusercontent.com",
},
{
protocol: "https",
hostname: "images.unsplash.com",
},
],
domains: ['images.unsplash.com'],
},
serverExternalPackages: ['@prisma/client'],
output: 'standalone',
};

export default nextConfig;
10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
"dev": "next dev -p 3001",
"build": "next build",
"start": "next start",
"start": "bash start.sh",
"lint": "next lint",
"db:seed": "tsx prisma/seed.ts"
"db:generate": "prisma generate",
"db:push": "prisma db push",
"db:migrate": "prisma migrate deploy",
"db:seed": "tsx prisma/seed.ts",
"postinstall": "prisma generate"
},
"dependencies": {
"@auth/prisma-adapter": "^2.10.0",
Expand Down
37 changes: 37 additions & 0 deletions railway-env.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Railway Environment Variables Setup

## Required Environment Variables for Railway:

### Database (Auto-configured by Railway)
- `DATABASE_URL` - Automatically set by Railway PostgreSQL service

### NextAuth Configuration
- `NEXTAUTH_URL` - Your Railway app URL (e.g., https://your-app.railway.app)
- `NEXTAUTH_SECRET` - Generate with: `openssl rand -base64 32`

### OAuth Providers (Optional for now)
- `GOOGLE_CLIENT_ID` - Your Google OAuth client ID
- `GOOGLE_CLIENT_SECRET` - Your Google OAuth client secret
- `GITHUB_CLIENT_ID` - Your GitHub OAuth client ID
- `GITHUB_CLIENT_SECRET` - Your GitHub OAuth client secret

## Setup Steps:

1. **In Railway Dashboard:**
- Go to your project
- Click on your app service
- Go to "Variables" tab
- Add the environment variables above

2. **Generate NEXTAUTH_SECRET:**
```bash
openssl rand -base64 32
```

3. **Set NEXTAUTH_URL:**
- Use your Railway app URL
- Format: https://your-app-name.railway.app

## Database Connection:
- Railway automatically provides `DATABASE_URL`
- No additional setup needed for database
9 changes: 9 additions & 0 deletions railway.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[build]
builder = "nixpacks"

[deploy]
startCommand = "npm start"
healthcheckPath = "/"
healthcheckTimeout = 300
restartPolicyType = "on_failure"
restartPolicyMaxRetries = 10
9 changes: 4 additions & 5 deletions src/app/api/cart/route.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { NextRequest, NextResponse } from "next/server"
import { getServerSession } from "next-auth"
import { authOptions } from "@/lib/auth"
import { auth } from "@/lib/auth"
import { prisma } from "@/lib/prisma"

export async function GET() {
try {
const session = await getServerSession(authOptions)
const session = await auth()

if (!session?.user?.email) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
Expand Down Expand Up @@ -44,7 +43,7 @@ export async function GET() {

export async function POST(request: NextRequest) {
try {
const session = await getServerSession(authOptions)
const session = await auth()

if (!session?.user?.email) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
Expand All @@ -68,7 +67,7 @@ export async function POST(request: NextRequest) {
// Add new cart items
if (items.length > 0) {
await prisma.cartItem.createMany({
data: items.map((item: any) => ({
data: items.map((item: { productId: string; quantity: number }) => ({
userId: user.id,
productId: item.productId,
quantity: item.quantity
Expand Down
1 change: 1 addition & 0 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import React from "react"
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
Expand Down
2 changes: 1 addition & 1 deletion src/app/products/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Notification } from "@/components/Notification";
import { SearchBar } from "@/components/SearchBar";
import { FeaturedCarousel } from "@/components/FeaturedCarousel";
import Image from "next/image";
import { LoadingSpinner, ProductCardSkeleton, CategorySkeleton } from "@/components/LoadingSpinner";
import { ProductCardSkeleton, CategorySkeleton } from "@/components/LoadingSpinner";
import { useEffect, useState } from "react";

interface Product {
Expand Down
44 changes: 22 additions & 22 deletions src/components/CartButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,32 @@ export function CartButton({ userId }: CartButtonProps) {
const [cartItemCount, setCartItemCount] = useState(0)

useEffect(() => {
loadCartItemCount()
}, [userId])

const loadCartItemCount = async () => {
if (userId) {
// Load from database for logged-in users
try {
const response = await fetch('/api/cart')
if (response.ok) {
const data = await response.json()
const count = data.reduce((total: number, item: any) => total + item.quantity, 0)
const loadCartItemCount = async () => {
if (userId) {
// Load from database for logged-in users
try {
const response = await fetch('/api/cart')
if (response.ok) {
const data = await response.json()
const count = data.reduce((total: number, item: { quantity: number }) => total + (item.quantity || 0), 0)
setCartItemCount(count)
}
} catch (error) {
console.error('Error loading cart count:', error)
}
} else {
// Load from localStorage for non-logged-in users
const savedCart = localStorage.getItem('cartzy-cart')
if (savedCart) {
const cartItems = JSON.parse(savedCart)
const count = cartItems.reduce((total: number, item: { quantity: number }) => total + (item.quantity || 0), 0)
setCartItemCount(count)
}
} catch (error) {
console.error('Error loading cart count:', error)
}
} else {
// Load from localStorage for non-logged-in users
const savedCart = localStorage.getItem('cartzy-cart')
if (savedCart) {
const cartItems = JSON.parse(savedCart)
const count = cartItems.reduce((total: number, item: any) => total + item.quantity, 0)
setCartItemCount(count)
}
}
}

loadCartItemCount()
}, [userId])

return (
<>
Expand Down
42 changes: 21 additions & 21 deletions src/components/Notification.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,32 +22,32 @@ export function Notification({ userId }: NotificationProps) {
const [unreadCount, setUnreadCount] = useState(0)

useEffect(() => {
loadNotifications()
}, [userId])

const loadNotifications = async () => {
if (userId) {
// Load from database for logged-in users
try {
const response = await fetch('/api/notifications')
if (response.ok) {
const data = await response.json()
const loadNotifications = async () => {
if (userId) {
// Load from database for logged-in users
try {
const response = await fetch('/api/notifications')
if (response.ok) {
const data = await response.json()
setNotifications(data)
setUnreadCount(data.filter((n: Notification) => !n.read).length)
}
} catch (error) {
console.error('Error loading notifications:', error)
}
} else {
// Load from localStorage for non-logged-in users
const savedNotifications = localStorage.getItem('cartzy-notifications')
if (savedNotifications) {
const data = JSON.parse(savedNotifications)
setNotifications(data)
setUnreadCount(data.filter((n: Notification) => !n.read).length)
}
} catch (error) {
console.error('Error loading notifications:', error)
}
} else {
// Load from localStorage for non-logged-in users
const savedNotifications = localStorage.getItem('cartzy-notifications')
if (savedNotifications) {
const data = JSON.parse(savedNotifications)
setNotifications(data)
setUnreadCount(data.filter((n: Notification) => !n.read).length)
}
}
}

loadNotifications()
}, [userId])

const markAsRead = async (notificationId: string) => {
const updatedNotifications = notifications.map(n =>
Expand Down
1 change: 1 addition & 0 deletions src/components/SearchBar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"use client"
import React from "react"

import { useState } from "react"
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline"
Expand Down
Loading