Skip to content

Commit d2c0c5c

Browse files
committed
feat: Enhance ImageModal with touch and drag support, zoom functionality, and improved styling
1 parent a428a4a commit d2c0c5c

File tree

3 files changed

+245
-54
lines changed

3 files changed

+245
-54
lines changed

README.md

Lines changed: 134 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,156 @@
1-
# Guide to Deploy a Container in AWS
1+
# AWS Deployment Guide and DevOps Project Hub
22

3-
This is a Next.js application that serves as a comprehensive guide for deploying containerized applications on AWS ECS Fargate. It includes a step-by-step workflow, prerequisites, and links to other DevOps projects.
3+
![Project Home](docs/guide_home.png)
44

5-
## Features
5+
This repository contains a Next.js application designed to serve as a comprehensive, interactive guide for deploying containerized applications to AWS ECS Fargate. It documents the entire DevOps lifecycle, from configuring AWS credentials to setting up a CI/CD pipeline with GitHub Actions.
66

7-
- **Interactive Guide**: Step-by-step instructions for AWS deployment.
8-
- **Modern UI**: Built with shadcn/ui and Tailwind CSS in a dark theme.
9-
- **Animations**: Smooth workflow animations using Framer Motion.
10-
- **Project Hub**: Links to other DevOps projects.
7+
The application itself is a hands-on learning tool, featuring a step-by-step workflow visualization, detailed command explanations, and a centralized dashboard for other DevOps projects.
118

12-
## Getting Started
9+
## Project Overview
1310

14-
First, run the development server:
11+
The primary goal of this project is to demystify the process of deploying a Dockerized application to the cloud. It breaks down complex AWS tasks into manageable steps, providing visual aids and direct links to necessary resources.
1512

16-
```bash
17-
npm run dev
18-
```
13+
### Key Features
1914

20-
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
15+
* **Interactive Deployment Workflow**: A guided experience through the AWS deployment process.
16+
* **Mobile-Optimized Design**: A responsive interface that works seamlessly on desktop and mobile devices.
17+
* **Automated CI/CD**: A fully functional GitHub Actions pipeline for continuous deployment.
18+
* **DevOps Dashboard**: A collection of links to related DevOps initiatives.
2119

22-
## CI/CD Pipeline (GitHub Actions)
20+
## Technical Architecture
2321

24-
This project includes a GitHub Actions workflow (`.github/workflows/deploy.yml`) that automatically builds and deploys the application to AWS ECS whenever changes are pushed to the `main` branch.
22+
* **Frontend Framework**: Next.js 14 (React)
23+
* **Styling**: Tailwind CSS
24+
* **UI Components**: Shadcn/UI, Lucide Icons
25+
* **Infrastructure**: AWS ECS (Fargate), AWS ECR, AWS IAM
26+
* **Containerization**: Docker
27+
* **CI/CD**: GitHub Actions
2528

26-
### Prerequisites for CI/CD
27-
You must configure the following **Secrets** in your GitHub Repository (Settings > Secrets and variables > Actions):
29+
## Deployment Walkthrough
2830

29-
| Secret Name | Description | Example Value |
30-
| :--- | :--- | :--- |
31-
| `AWS_ACCESS_KEY_ID` | Your AWS Access Key ID | `AKIA...` |
32-
| `AWS_SECRET_ACCESS_KEY` | Your AWS Secret Access Key | `wJalr...` |
33-
| `AWS_REGION` | AWS Region | `us-west-2` |
34-
| `ECR_REPOSITORY` | Name of your ECR Repository | `aws-deploy-guide` |
35-
| `ECS_CLUSTER` | Name of your ECS Cluster | `aws-deploy-guide-cluster` |
36-
| `ECS_SERVICE` | Name of your ECS Service | `aws-deploy-guide-task-service` |
37-
| `ECS_TASK_DEFINITION` | Name of your Task Definition Family | `aws-deploy-guide-task` |
38-
| `CONTAINER_NAME` | Name of the container in Task Def | `aws-deploy-guide-container` |
31+
This section documents every step required to deploy the application, corresponding to the interactive guide within the app.
3932

40-
### Workflow Stages
41-
1. **Build and Push:** Builds the Docker image and pushes it to Amazon ECR.
42-
2. **Deploy to ECS:** Updates the ECS Task Definition with the new image and deploys it to the ECS Service.
33+
### 1. AWS Configuration
4334

44-
## Docker Deployment
35+
The first step involves setting up the necessary permissions and tools to interact with AWS.
4536

46-
This project includes a `Dockerfile` to containerize the application.
37+
**Create Access Keys**
38+
Generate programmatic access keys in the AWS IAM Console to allow the AWS CLI to authenticate.
39+
![AWS Access Keys](docs/access_keys_created.png)
4740

48-
### Build the Docker Image
41+
### 2. Container Registry (ECR)
4942

50-
```bash
51-
docker build -t aws-deploy-guide .
52-
```
43+
Amazon Elastic Container Registry (ECR) is used to store the Docker images.
5344

54-
### Run the Container Locally
45+
**Create Repository**
46+
A new repository is created to host the application image.
47+
![ECR Repository List](docs/repo_list.png)
5548

56-
```bash
57-
docker run -p 3000:3000 aws-deploy-guide
58-
```
49+
**Push Commands**
50+
The AWS CLI provides specific commands to authenticate Docker and push the image.
51+
![Push Commands](docs/push_commands.png)
5952

60-
Access the application at `http://localhost:3000`.
53+
**Successful Push**
54+
Verifying that the image has been successfully uploaded to ECR.
55+
![CLI Push Success](docs/cli_push_success.png)
6156

62-
## AWS Deployment Steps (Summary)
57+
**Repository Content**
58+
The ECR repository now contains the tagged Docker image.
59+
![ECR Repo with Image](docs/ecr_repo_with_image.png)
6360

64-
1. **Login to AWS**: Configure CLI and login to ECR.
65-
2. **Push Image**: Tag and push your Docker image to ECR.
66-
3. **Create Cluster**: Set up an ECS Fargate cluster.
67-
4. **Deploy Service**: Create a task definition and service to run your container.
68-
5. **Access**: Use the public IP assigned to your task.
61+
### 3. ECS Cluster Setup
6962

70-
For detailed steps, run the application and follow the interactive guide.
63+
Amazon Elastic Container Service (ECS) is the orchestration service used to run the containers.
64+
65+
**Cluster Creation Form**
66+
Configuring a new Fargate cluster in the AWS Console.
67+
![ECS Cluster Creation Form](docs/ecs_cluster_creation_form.png)
68+
69+
**Cluster Created**
70+
The ECS Cluster is successfully provisioned and ready to host services.
71+
![ECS Cluster Created](docs/ecs_cluster_created.png)
72+
73+
### 4. Task Definition
74+
75+
The Task Definition serves as a blueprint for the application, specifying the container image, CPU, and memory requirements.
76+
77+
**Configure Task Definition (Step 1)**
78+
Setting the task family name and infrastructure requirements.
79+
![New Task Definition Form 1](docs/new_task_definition_form_1.png)
80+
81+
**Configure Container (Step 2)**
82+
Specifying the container details and port mappings.
83+
![New Task Definition Form 2](docs/new_task_definition_form_2.png)
84+
85+
**Select Image URI**
86+
Using the URI of the image pushed to ECR earlier.
87+
![ECR Image Selection](docs/ecr_image_selection_on_new_task_def.png)
88+
89+
**Task Definition Created**
90+
The task definition is successfully registered.
91+
![Task Definition Success](docs/task_definition_creation_success.png)
92+
93+
### 5. Service Deployment
94+
95+
The ECS Service maintains the desired number of tasks and handles networking.
96+
97+
**Create Service (Step 1)**
98+
Defining the service name and desired task count.
99+
![Create Service Form 1](docs/create_service_form_1.png)
100+
101+
**Environment Configuration (Step 2)**
102+
Setting up the deployment type and platform version.
103+
![Create Service Form 2](docs/create_service_form_2_env.png)
104+
105+
**Deployment Settings (Step 3)**
106+
Configuring deployment strategies (e.g., rolling updates).
107+
![Create Service Form 3](docs/create_service_form_3_deployment.png)
108+
109+
**Networking (Step 4)**
110+
Defining the VPC, subnets, and security groups for the service.
111+
![Create Service Form 4](docs/create_service_form_4_networking.png)
112+
113+
**Service Created**
114+
The service is successfully created and begins provisioning tasks.
115+
![Service Creation Success](docs/service_creation_success.png)
116+
117+
### 6. Verification
118+
119+
Once the service is stable, the application is accessible via the public IP of the Fargate task.
120+
121+
**Access Application**
122+
The application is live and accessible in the browser.
123+
![Access Application](docs/access_application.png)
124+
125+
**Deployed Dashboard**
126+
The live dashboard showing the successful deployment.
127+
![Deployed Application](docs/deployed_application.png)
128+
129+
## CI/CD Pipeline
130+
131+
The project includes a GitHub Actions workflow to automate the build and deploy process.
132+
133+
**GitHub Secrets**
134+
Securely storing AWS credentials and configuration variables in GitHub.
135+
![GitHub Secrets](docs/github_secrets.png)
136+
137+
**Workflow Visualization**
138+
The GitHub Actions interface showing the progress of the build and deploy jobs.
139+
![GitHub Workflow](docs/github_workflow.png)
140+
141+
### Required Secrets
142+
143+
To enable the CI/CD pipeline, the following secrets must be configured in the GitHub Repository:
144+
145+
* AWS_ACCESS_KEY_ID
146+
* AWS_SECRET_ACCESS_KEY
147+
* AWS_REGION
148+
* ECR_REPOSITORY
149+
* ECS_CLUSTER
150+
* ECS_SERVICE
151+
* ECS_TASK_DEFINITION
152+
* CONTAINER_NAME
153+
154+
## Educational Project Disclaimer
155+
156+
This is an educational project designed to demonstrate DevOps best practices and modern web development techniques. Anyone is free to fork this repository and modify anything to suit their own learning needs or project requirements.

aws-deploy-guide/src/components/ui/ImageModal.tsx

Lines changed: 111 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { motion, AnimatePresence } from "framer-motion";
44
import { X } from "lucide-react";
5-
import { useEffect } from "react";
5+
import { useEffect, useState } from "react";
66

77
interface ImageModalProps {
88
isOpen: boolean;
@@ -12,6 +12,11 @@ interface ImageModalProps {
1212
}
1313

1414
export function ImageModal({ isOpen, onClose, imageSrc, altText }: ImageModalProps) {
15+
const [scale, setScale] = useState(1);
16+
const [position, setPosition] = useState({ x: 0, y: 0 });
17+
const [isDragging, setIsDragging] = useState(false);
18+
const [dragStart, setDragStart] = useState({ x: 0, y: 0 });
19+
1520
useEffect(() => {
1621
const handleEsc = (e: KeyboardEvent) => {
1722
if (e.key === "Escape") onClose();
@@ -20,33 +25,133 @@ export function ImageModal({ isOpen, onClose, imageSrc, altText }: ImageModalPro
2025
return () => window.removeEventListener("keydown", handleEsc);
2126
}, [onClose]);
2227

28+
useEffect(() => {
29+
if (isOpen) {
30+
setScale(1);
31+
setPosition({ x: 0, y: 0 });
32+
}
33+
}, [isOpen]);
34+
35+
const handleWheel = (e: React.WheelEvent) => {
36+
e.preventDefault();
37+
const newScale = Math.max(1, Math.min(5, scale + (e.deltaY > 0 ? -0.2 : 0.2)));
38+
setScale(newScale);
39+
if (newScale === 1) setPosition({ x: 0, y: 0 });
40+
};
41+
42+
const handleTouchStart = (e: React.TouchEvent) => {
43+
if (e.touches.length === 2) {
44+
const touch1 = e.touches[0];
45+
const touch2 = e.touches[1];
46+
const distance = Math.hypot(touch2.clientX - touch1.clientX, touch2.clientY - touch1.clientY);
47+
(e.currentTarget as HTMLElement).dataset.pinchDistance = distance.toString();
48+
} else if (scale > 1) {
49+
setIsDragging(true);
50+
setDragStart({ x: e.touches[0].clientX - position.x, y: e.touches[0].clientY - position.y });
51+
}
52+
};
53+
54+
const handleTouchMove = (e: React.TouchEvent) => {
55+
if (e.touches.length === 2) {
56+
e.preventDefault();
57+
const touch1 = e.touches[0];
58+
const touch2 = e.touches[1];
59+
const distance = Math.hypot(touch2.clientX - touch1.clientX, touch2.clientY - touch1.clientY);
60+
const prevDistance = parseFloat((e.currentTarget as HTMLElement).dataset.pinchDistance || "0");
61+
if (prevDistance > 0) {
62+
const newScale = Math.max(1, Math.min(5, scale * (distance / prevDistance)));
63+
setScale(newScale);
64+
if (newScale === 1) setPosition({ x: 0, y: 0 });
65+
}
66+
(e.currentTarget as HTMLElement).dataset.pinchDistance = distance.toString();
67+
} else if (isDragging && scale > 1) {
68+
e.preventDefault();
69+
setPosition({
70+
x: e.touches[0].clientX - dragStart.x,
71+
y: e.touches[0].clientY - dragStart.y
72+
});
73+
}
74+
};
75+
76+
const handleTouchEnd = () => {
77+
setIsDragging(false);
78+
};
79+
80+
const handleMouseDown = (e: React.MouseEvent) => {
81+
if (scale > 1) {
82+
setIsDragging(true);
83+
setDragStart({ x: e.clientX - position.x, y: e.clientY - position.y });
84+
}
85+
};
86+
87+
const handleMouseMove = (e: React.MouseEvent) => {
88+
if (isDragging && scale > 1) {
89+
setPosition({
90+
x: e.clientX - dragStart.x,
91+
y: e.clientY - dragStart.y
92+
});
93+
}
94+
};
95+
96+
const handleMouseUp = () => {
97+
setIsDragging(false);
98+
};
99+
100+
const handleDoubleTap = () => {
101+
if (scale === 1) {
102+
setScale(2.5);
103+
} else {
104+
setScale(1);
105+
setPosition({ x: 0, y: 0 });
106+
}
107+
};
108+
23109
return (
24110
<AnimatePresence>
25111
{isOpen && (
26112
<motion.div
27113
initial={{ opacity: 0 }}
28114
animate={{ opacity: 1 }}
29115
exit={{ opacity: 0 }}
30-
className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm p-4"
116+
className="fixed inset-0 z-50 flex items-center justify-center bg-black/90 backdrop-blur-sm"
31117
onClick={onClose}
32118
>
33119
<motion.div
34120
initial={{ scale: 0.9, opacity: 0 }}
35121
animate={{ scale: 1, opacity: 1 }}
36122
exit={{ scale: 0.9, opacity: 0 }}
37-
className="relative w-full h-full flex items-center justify-center p-4"
123+
className="relative w-full h-full flex items-center justify-center overflow-hidden touch-none"
38124
onClick={(e) => e.stopPropagation()}
125+
onWheel={handleWheel}
126+
onTouchStart={handleTouchStart}
127+
onTouchMove={handleTouchMove}
128+
onTouchEnd={handleTouchEnd}
129+
onMouseDown={handleMouseDown}
130+
onMouseMove={handleMouseMove}
131+
onMouseUp={handleMouseUp}
132+
onMouseLeave={handleMouseUp}
133+
onDoubleClick={handleDoubleTap}
39134
>
40135
<button
41136
onClick={onClose}
42-
className="absolute top-4 right-4 z-50 p-2 bg-black/50 rounded-full text-white hover:bg-white/20 transition-colors backdrop-blur-md"
137+
className="absolute top-2 right-2 md:top-4 md:right-4 z-50 p-2 bg-black/70 rounded-full text-white hover:bg-white/20 transition-colors backdrop-blur-md"
43138
>
44-
<X size={32} />
139+
<X size={24} className="md:w-8 md:h-8" />
45140
</button>
141+
<div className="absolute top-2 left-2 md:top-4 md:left-4 z-50 px-3 py-1 bg-black/70 rounded-full text-white text-xs md:text-sm backdrop-blur-md">
142+
{Math.round(scale * 100)}%
143+
</div>
46144
<img
47145
src={imageSrc}
48146
alt={altText}
49-
className="max-w-full max-h-full w-auto h-auto object-contain rounded-lg shadow-2xl border border-white/10"
147+
className="w-full h-full object-contain select-none"
148+
style={{
149+
transform: `scale(${scale}) translate(${position.x / scale}px, ${position.y / scale}px)`,
150+
transition: isDragging ? 'none' : 'transform 0.2s ease-out',
151+
cursor: scale > 1 ? (isDragging ? 'grabbing' : 'grab') : 'default',
152+
touchAction: 'none'
153+
}}
154+
draggable={false}
50155
/>
51156
</motion.div>
52157
</motion.div>

docs/guide_home.png

188 KB
Loading

0 commit comments

Comments
 (0)