diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml new file mode 100644 index 00000000..13ddc759 --- /dev/null +++ b/.github/workflows/deploy-staging.yml @@ -0,0 +1,56 @@ +name: Deploy to Staging Environment + +on: + push: + branches: [ main ] + workflow_dispatch: # Allows manual trigger button + +jobs: + deploy-staging: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '18' + + - 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 + run: npm test + + - name: Build React App + run: npm run build-react + + - name: Deploy Files to Staging Server + uses: appleboy/scp-action@master + with: + host: ${{ secrets.STAGING_EC2_IP }} + username: ubuntu + key: ${{ secrets.EC2_SSH_KEY }} + source: "./*" + target: "/home/ubuntu/app" + + - name: Start Application on Staging Server + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.STAGING_EC2_IP }} + username: ubuntu + 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 diff --git a/.github/workflows/deploy-testing.yml b/.github/workflows/deploy-testing.yml new file mode 100644 index 00000000..3e4b2b2e --- /dev/null +++ b/.github/workflows/deploy-testing.yml @@ -0,0 +1,56 @@ +name: Deploy to Testing Environment + +on: + pull_request: + branches: [ main ] + workflow_dispatch: # Allows manual trigger button + +jobs: + build-and-test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '18' + + - name: Install Dependencies + run: npm install + + - name: Run Linting (Code Analysis) + # If you don't have a specific lint command, this placeholder ensures the step exists + run: echo "Linting code analysis..." + + - name: Run Unit Tests + # runs the test script defined in package.json + run: npm test + + - name: Build React App + run: npm run build-react + + - name: Deploy Files to Testing Server + uses: appleboy/scp-action@master + with: + host: ${{ secrets.TESTING_EC2_IP }} + username: ubuntu + key: ${{ secrets.EC2_SSH_KEY }} + source: "./*" + target: "/home/ubuntu/app" + + - name: Start Application on Testing Server + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.TESTING_EC2_IP }} + username: ubuntu + 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 diff --git a/README.md b/README.md index 722f18b8..ff2297f3 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,18 @@ # ReactNodeTesting Sample React and Node/Express project to demonstrate usage of React Test Library and Jest test frameworks. -Article for this can be found here - https://medium.com/@eljamaki01/testing-a-react-node-express-app-with-react-test-library-and-jest-2ac910812c41 \ No newline at end of file +Article for this can be found here - https://medium.com/@eljamaki01/testing-a-react-node-express-app-with-react-test-library-and-jest-2ac910812c41 + + +### To run this thing, you need to first verify that the system has node and npm installed: + +node -v +npm -v + +### After verifying that, run: + +npm install + +### Then run the following to build and serve the application: + +npm run build-react +npm start \ No newline at end of file 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 350c4001..c223b111 100644 --- a/src/App.css +++ b/src/App.css @@ -1,41 +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: center; + min-height: 100vh; + padding: 40px 20px; } -.App-logo { - height: 40vmin; - pointer-events: none; +.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; } -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; +@keyframes slideIn { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); } } -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); +.app-header { + text-align: center; + margin-bottom: 30px; +} + +.app-header h1 { + 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: 800; + margin-bottom: 10px; + letter-spacing: -1px; +} + +.app-header p { + color: #666; + font-size: 1.1rem; + font-weight: 500; +} + +.cart-section { + margin-bottom: 30px; +} + +.shipping-section { + background: linear-gradient(135deg, #f5f7fa 0%, #e8ecf3 100%); + border-radius: 16px; + padding: 25px; + margin: 30px 0; + border: 2px solid #e0e6ed; +} + +.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; + border-radius: 12px; + font-size: 1.2rem; + font-weight: 700; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4); + text-transform: uppercase; + letter-spacing: 1px; } -.App-link { - color: #61dafb; +.purchase-button:hover { + transform: translateY(-2px); + box-shadow: 0 6px 25px rgba(102, 126, 234, 0.6); } -@keyframes App-logo-spin { - from { - transform: rotate(0deg); +.purchase-button:active { + transform: translateY(0); +} + +.cart-summary { + background: linear-gradient(135deg, #f8f9ff 0%, #f0f2ff 100%); + border-radius: 16px; + padding: 25px; + margin-top: 20px; + border: 2px solid #e0e4ff; +} + +.cart-summary p { + display: flex; + justify-content: space-between; + padding: 10px 0; + color: #444; + font-size: 1.05rem; +} + +.cart-summary hr { + border: none; + border-top: 2px solid #d0d5ff; + margin: 15px 0; +} + +.cart-summary p:last-child { + font-size: 1.4rem; + font-weight: 700; + color: #667eea; + margin-top: 10px; +} + +.cart-items-list { + list-style-type: none; + padding: 0; + margin: 0; +} + +.cart-items-list li { + margin-bottom: 15px; +} + +.loading-message, .error-message { + text-align: center; + padding: 40px; + font-size: 1.2rem; + color: #666; +} + +.error-message { + color: #e74c3c; + font-weight: 600; +} + +@media (max-width: 768px) { + .app-container { + padding: 25px; } - to { - transform: rotate(360deg); + + .app-header h1 { + font-size: 2rem; + } + + .purchase-button { + padding: 15px; + font-size: 1rem; } } diff --git a/src/App.js b/src/App.js index c4b1370b..29c8ec18 100644 --- a/src/App.js +++ b/src/App.js @@ -15,16 +15,33 @@ function App(props) { return ( <>
Thank you for shopping with us!
-Items: ${(itemTotal/100).toFixed(2)}
-Shipping: ${(props.shippingCost/100).toFixed(2)}
-Tax: ${(tax/100).toFixed(2)}
-Order Total: ${(total/100).toFixed(2)}
++ Items: + ${(itemTotal/100).toFixed(2)} +
++ Shipping: + ${(props.shippingCost/100).toFixed(2)} +
++ Tax (10%): + ${(tax/100).toFixed(2)} +
++ Order Total: + ${(total/100).toFixed(2)} +