diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml index 13ddc759..7a50c467 100644 --- a/.github/workflows/deploy-staging.yml +++ b/.github/workflows/deploy-staging.yml @@ -3,7 +3,7 @@ name: Deploy to Staging Environment on: push: branches: [ main ] - workflow_dispatch: # Allows manual trigger button + workflow_dispatch: jobs: deploy-staging: @@ -21,36 +21,44 @@ jobs: - name: Install Dependencies run: npm install - - name: Run Linting (Code Analysis) - # Placeholder for linting logic - run: echo "Linting code analysis..." - - - name: Run Unit Tests - # Ensures code is verified before deploying to Staging + - name: Run Tests run: npm test - name: Build React App run: npm run build-react - - name: Deploy Files to Staging Server + - name: Copy Files to Staging Server uses: appleboy/scp-action@master with: - host: ${{ secrets.STAGING_EC2_IP }} - username: ubuntu + host: ${{ secrets.HOST_STAGING }} + username: ${{ secrets.USERNAME }} key: ${{ secrets.EC2_SSH_KEY }} source: "./*" target: "/home/ubuntu/app" - - name: Start Application on Staging Server + - name: Start Application on Staging uses: appleboy/ssh-action@master with: - host: ${{ secrets.STAGING_EC2_IP }} - username: ubuntu + host: ${{ secrets.HOST_STAGING }} + username: ${{ secrets.USERNAME }} key: ${{ secrets.EC2_SSH_KEY }} script: | cd /home/ubuntu/app - # Install production dependencies npm install --production - # Restart the app using PM2 (or start if not running) pm2 restart all || pm2 start index.js --name "react-node-app" - pm2 save \ No newline at end of file + pm2 save + + - name: Send Success Email + if: success() + uses: dawidd6/action-send-mail@v3 + with: + server_address: smtp.gmail.com + server_port: 465 + username: ${{ secrets.MAIL_USERNAME }} + password: ${{ secrets.MAIL_PASSWORD }} + subject: Deployment to STAGING Successful + body: | + Changes have been merged to main and deployed to Staging. + Access it here: http://${{ secrets.HOST_STAGING }}:3000 + to: ${{ secrets.QA_EMAIL }} + from: DevOps Automation diff --git a/carts.js b/carts.js index 8b80bd21..1f5a4048 100644 --- a/carts.js +++ b/carts.js @@ -16,6 +16,24 @@ let activeCarts = [ description: "Assorted sizes, 2 cartons", cost: 295, imageUrl: "blackberries.jpg" + }, + { + title: "Delicious Cookies", + description: "Homemade chocolate chip cookies, 1 dozen", + cost: 450, + imageUrl: "cookies.jpg" + }, + { + title: "Organic Blueberries", + description: "Fresh picked, 3 cartons", + cost: 350, + imageUrl: "blackberries.jpg" + }, + { + title: "Gourmet Cookie Box", + description: "Assorted flavors, premium selection", + cost: 599, + imageUrl: "cookies.jpg" } ] }, diff --git a/package.json b/package.json index 958fdf97..7da51be7 100644 --- a/package.json +++ b/package.json @@ -45,5 +45,6 @@ }, "devDependencies": { "supertest": "^6.2.2" - } + }, + "proxy": "http://localhost:3001" } diff --git a/src/App.css b/src/App.css index 72dc7379..c223b111 100644 --- a/src/App.css +++ b/src/App.css @@ -1,194 +1,173 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + min-height: 100vh; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; +} + .App { display: flex; flex-direction: column; align-items: center; - justify-content: flex-start; + justify-content: center; min-height: 100vh; - padding: 2rem 1rem; - max-width: 1200px; - margin: 0 auto; + padding: 40px 20px; +} + +.app-container { + background: white; + border-radius: 24px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); + padding: 40px; + max-width: 900px; + width: 100%; + animation: slideIn 0.5s ease-out; +} + +@keyframes slideIn { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } } .app-header { text-align: center; - margin-bottom: 2rem; - width: 100%; + margin-bottom: 30px; } .app-header h1 { - color: #ffffff; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; font-size: 2.5rem; - font-weight: 700; - margin: 0 0 0.5rem 0; - text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2); - letter-spacing: -0.5px; + font-weight: 800; + margin-bottom: 10px; + letter-spacing: -1px; } .app-header p { - color: #ffffff; - font-size: 1.2rem; - margin: 0; - opacity: 0.95; - font-weight: 300; + color: #666; + font-size: 1.1rem; + font-weight: 500; } -.cart-container { - background: #ffffff; - border-radius: 16px; - box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15); - padding: 2rem; - width: 100%; - margin-bottom: 2rem; - transition: transform 0.3s ease, box-shadow 0.3s ease; +.cart-section { + margin-bottom: 30px; } -.cart-container:hover { - transform: translateY(-2px); - box-shadow: 0 15px 50px rgba(0, 0, 0, 0.2); +.shipping-section { + background: linear-gradient(135deg, #f5f7fa 0%, #e8ecf3 100%); + border-radius: 16px; + padding: 25px; + margin: 30px 0; + border: 2px solid #e0e6ed; } -.shipping-container { - background: #ffffff; - border-radius: 16px; - box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15); - padding: 2rem; - width: 100%; - margin-bottom: 2rem; +.shipping-section h4 { + color: #333; + font-size: 1.2rem; + margin-bottom: 15px; + font-weight: 600; } .purchase-button { + width: 100%; + padding: 18px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; - padding: 1rem 3rem; - font-size: 1.1rem; - font-weight: 600; border-radius: 12px; + font-size: 1.2rem; + font-weight: 700; cursor: pointer; - box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4); transition: all 0.3s ease; + box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4); text-transform: uppercase; letter-spacing: 1px; - width: 100%; - max-width: 400px; - margin: 0 auto; - display: block; } .purchase-button:hover { transform: translateY(-2px); - box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6); - background: linear-gradient(135deg, #764ba2 0%, #667eea 100%); + box-shadow: 0 6px 25px rgba(102, 126, 234, 0.6); } .purchase-button:active { transform: translateY(0); - box-shadow: 0 2px 10px rgba(102, 126, 234, 0.4); -} - -.cart-items-list { - list-style-type: none; - padding: 0; - margin: 0; - display: flex; - flex-direction: column; - gap: 1rem; } .cart-summary { - background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); - border-radius: 12px; - padding: 1.5rem; - margin-top: 2rem; - border: 1px solid rgba(0, 0, 0, 0.05); + background: linear-gradient(135deg, #f8f9ff 0%, #f0f2ff 100%); + border-radius: 16px; + padding: 25px; + margin-top: 20px; + border: 2px solid #e0e4ff; } .cart-summary p { - margin: 0.75rem 0; - font-size: 1rem; - color: #333; display: flex; justify-content: space-between; - align-items: center; + padding: 10px 0; + color: #444; + font-size: 1.05rem; } .cart-summary hr { border: none; - border-top: 2px solid rgba(102, 126, 234, 0.3); - margin: 1rem 0; + border-top: 2px solid #d0d5ff; + margin: 15px 0; } -.cart-summary .order-total { - font-size: 1.5rem; +.cart-summary p:last-child { + font-size: 1.4rem; font-weight: 700; color: #667eea; - margin-top: 1rem; - padding-top: 1rem; - border-top: 2px solid rgba(102, 126, 234, 0.3); + margin-top: 10px; } -.cart-title { - color: #333; - font-size: 2rem; - font-weight: 700; - margin: 0 0 1.5rem 0; - padding-bottom: 1rem; - border-bottom: 3px solid #667eea; +.cart-items-list { + list-style-type: none; + padding: 0; + margin: 0; } -.shipping-title { - color: #333; - font-size: 1.5rem; - font-weight: 600; - margin: 0 0 1.5rem 0; +.cart-items-list li { + margin-bottom: 15px; } -.loading-text, -.error-text { +.loading-message, .error-message { text-align: center; - padding: 2rem; - color: #333; + padding: 40px; font-size: 1.2rem; + color: #666; } -.error-text { +.error-message { color: #e74c3c; - background: #ffeaea; - border-radius: 8px; - border-left: 4px solid #e74c3c; -} - -.shipping-option-label { - display: flex; - align-items: center; - padding: 1rem; - border-radius: 8px; - border: 2px solid #e0e0e0; - transition: all 0.3s ease; - cursor: pointer; -} - -.shipping-option-label:hover { - border-color: #667eea; - background-color: #f8f9ff; + font-weight: 600; } @media (max-width: 768px) { - .App { - padding: 1rem 0.5rem; + .app-container { + padding: 25px; } - + .app-header h1 { font-size: 2rem; } - - .cart-container, - .shipping-container { - padding: 1.5rem; - } - + .purchase-button { - padding: 0.875rem 2rem; + padding: 15px; font-size: 1rem; } } diff --git a/src/App.js b/src/App.js index 3e1b17b1..29c8ec18 100644 --- a/src/App.js +++ b/src/App.js @@ -15,26 +15,33 @@ function App(props) { return ( <>
-
-

🛍️ Shopping Cart

-

Thank you for shopping with us!

+
+
+

🛒 Your Shopping Cart

+

Thank you for shopping with us!

+
+ +
+ +
+ +
+ +
+ +
-
- -
-
- -
-
({ root: { display: 'flex', borderRadius: '12px', - boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)', - transition: 'all 0.3s ease', overflow: 'hidden', - marginBottom: '0.5rem', + transition: 'all 0.3s ease', + border: '1px solid #e0e6ed', '&:hover': { - transform: 'translateY(-2px)', - boxShadow: '0 6px 20px rgba(0, 0, 0, 0.15)', - }, + transform: 'translateY(-4px)', + boxShadow: '0 8px 20px rgba(0, 0, 0, 0.15)', + } }, details: { display: 'flex', flexDirection: 'column', - flex: '1 1 auto', - padding: '1rem', + flex: 1, }, content: { flex: '1 0 auto', - padding: '0.5rem 1rem !important', + padding: '20px', }, cover: { - height: '120px', - width: '120px', - minWidth: '120px', + width: 150, + minHeight: 150, objectFit: 'cover', - background: 'linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%)', - }, - title: { - fontWeight: 600, - color: '#333', - marginBottom: '0.5rem', - fontSize: '1.1rem', - }, - description: { - color: '#666', - marginBottom: '0.5rem', - fontSize: '0.9rem', - }, - price: { - color: '#667eea', - fontWeight: 700, - fontSize: '1.2rem', - marginTop: '0.5rem', - }, + } })); export default function CartItem(props) { const classes = useStyles(); return ( - +
- + {props.item.title} - + {props.item.description} - + ${(props.item.cost/100).toFixed(2)} diff --git a/src/cartview.js b/src/cartview.js index 6b2227a7..9f456311 100644 --- a/src/cartview.js +++ b/src/cartview.js @@ -32,9 +32,17 @@ function CartView(props) { }, [props.cartId]); if (error) { - return (

Failed to retrieve cart ({error.message})

); + return ( +
+

❌ Failed to retrieve cart ({error.message})

+
+ ); } else if (!cart) { - return (

Loading shopping cart...

); + return ( +
+

⏳ Loading shopping cart...

+
+ ); } else { const costs = cart.cartItems.map(a => a.cost); const itemTotal = sum(...costs); @@ -43,7 +51,9 @@ function CartView(props) { return ( <>
-

Shopping Cart

+

+ 🛍️ Your Items +

    {cart.cartItems.map((cartItemData, idx) =>
  • @@ -62,11 +72,11 @@ function CartView(props) { ${(props.shippingCost/100).toFixed(2)}

    - Tax: + Tax (10%): ${(tax/100).toFixed(2)}

    -
    -

    +


    +

    Order Total: ${(total/100).toFixed(2)}

    diff --git a/src/msgYNModal.js b/src/msgYNModal.js index b90d4cab..9bbafdd9 100644 --- a/src/msgYNModal.js +++ b/src/msgYNModal.js @@ -76,25 +76,33 @@ function MsgYNModal(props) { classes={{ paper: classes.dialog }} {...other} > - - Confirmation Of Order + + ✅ Confirmation Of Order - - + + {message} - + diff --git a/src/shippingOptions.js b/src/shippingOptions.js index b6960b1b..c6ac9cf4 100644 --- a/src/shippingOptions.js +++ b/src/shippingOptions.js @@ -64,8 +64,8 @@ function ShippingOptions(props) { } return ( -
    -

    Choose your delivery option:

    +
    +

    🚚 Choose your delivery option:

    handleChange(event, value)} + style={{ gap: '10px', flexWrap: 'wrap' }} > } - label="Free 10 day shipping" - classes={{ root: `${classes.radioLabel} ${shipping === 0 ? classes.radioLabelChecked : ''}` }} + control={} + label="🆓 Free 10 day shipping" + style={{ margin: '5px 0' }} /> } - label="$5.00 2-day shipping" - classes={{ root: `${classes.radioLabel} ${shipping === 500 ? classes.radioLabelChecked : ''}` }} + control={} + label="⚡ $5.00 2-day shipping" + style={{ margin: '5px 0' }} /> } - label="$20.00 overnight shipping" - classes={{ root: `${classes.radioLabel} ${shipping === 2000 ? classes.radioLabelChecked : ''}` }} + control={} + label="🚀 $20.00 overnight shipping" + style={{ margin: '5px 0' }} />