Skip to content
Open
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
31 changes: 31 additions & 0 deletions chat-widget/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Dependencias
node_modules
npm-debug.log

# Build output
dist
.angular

# IDE
.vscode
.idea
*.swp
*.swo

# Git
.git
.gitignore

# Tests
coverage

# OS
.DS_Store
Thumbs.db

# Logs
*.log

# Environment
.env
.env.local
16 changes: 16 additions & 0 deletions chat-widget/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Editor configuration, see https://editorconfig.org
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true

[*.ts]
quote_type = single

[*.md]
max_line_length = off
trim_trailing_whitespace = false
43 changes: 43 additions & 0 deletions chat-widget/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.

# Compiled output
/dist
/tmp
/out-tsc
/bazel-out

# Node
node_modules/
npm-debug.log
yarn-error.log
package-lock.json

# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace

# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*

# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings

# System files
.DS_Store
Thumbs.db
13 changes: 13 additions & 0 deletions chat-widget/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM node:18-alpine

WORKDIR /app

COPY package*.json ./

RUN npm ci

COPY . .

EXPOSE 4200

CMD ["npm", "start", "--", "--host", "0.0.0.0", "--poll", "2000"]
114 changes: 114 additions & 0 deletions chat-widget/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# 1millionBot Chat Widget

## 📋 Description

Reusable chat widget implementation for chatbots, developed as part of the 1millionbot frontend challenge.

## 🏗️ Architecture

The project follows a **modular architecture** and clean-code principles. It is structured so the chat module can be extracted into an Angular library in the future with minimal changes.

### Folder structure

```

src/app/
├── core/ # Business logic and base services
│ ├── models/ # Interfaces, types and enums
│ └── services/ # Core services (ChatService, StateService)
├── chat/ # Chat module (reusable)
│ └── components/ # Chat widget components
├── features/ # Demo-specific features
│ └── mock-bot/ # Mock chatbot implementation
└── shared/ # Shared utilities
├── pipes/ # Custom pipes
└── directives/ # Custom directives

```

## ✨ Features

### Core requirements

- ✅ Chat window with a modern UI
- ✅ Messages visually distinguished (bot vs user)
- ✅ Bot avatar and name always visible
- ✅ Dropdown menu (forget user data, change language, privacy)
- ✅ Multiple message types:
- Text
- Image cards
- Interactive buttons
- ✅ Responsive design (fullscreen on mobile)

### Bonus

- ✅ Pre-chat call to action
- ✅ Internationalization (i18n) ES/EN
- ✅ Unit tests (Jasmine/Karma)
- ✅ E2E tests (Cypress)
- ✅ Dockerized

## 🚀 Installation & Usage

```bash
# Install dependencies
npm install

# Development server
npm start
# Open http://localhost:4200

# Production build
npm run build

# Tests
npm test # Unit tests
npm run e2e # E2E tests

# Docker
docker-compose up # Run with Docker
```

## 🛠️ Tech Stack

- **Angular 16+**
- **TypeScript** (strict mode)
- **Tailwind CSS** (utility-first styling)
- **RxJS**
- **Jasmine/Karma** (unit tests)
- **Cypress** (E2E tests)
- **Docker**

## 📝 Technical Decisions

### Why this architecture?

1. **Separation of concerns**: Core/Chat/Features/Shared keeps the code organized
2. **Reusable**: The `ChatModule` is structured so it can be extracted into a library
3. **Testable**: Dependency injection and small services make testing straightforward
4. **Scalable**: Easy to add new message types or features

### Design Patterns

- **Smart/Dumb components**: Container vs presentational components
- **Reactive**: BehaviorSubjects for reactive state
- **Dependency Injection**: Interfaces and injectable services

## 👨‍💻 Development

### Coding Conventions

- TypeScript strict mode
- ESLint configured
- Prettier for formatting
- Semantic commits (feat:, fix:, docs:, etc.)

### Project Status

See the root-level TODO list for progress: `../README.md`.

---

**Author:** Juan Manuel Pérez
**Challenge:** 1millionbot Frontend Developer
**Date:** November 2025
108 changes: 108 additions & 0 deletions chat-widget/angular.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"cli": {
"packageManager": "npm",
"analytics": "261ca842-37a4-4c4c-87c9-20a5ccf8feab"
},
"newProjectRoot": "projects",
"projects": {
"chat-app": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/chat-app",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": [
"zone.js"
],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"outputHashing": "all"
},
"development": {
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "chat-app:build:production"
},
"development": {
"browserTarget": "chat-app:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "chat-app:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"polyfills": [
"zone.js",
"zone.js/testing"
],
"tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": []
}
}
}
}
}
}
17 changes: 17 additions & 0 deletions chat-widget/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
version: "3.8"

services:
chat-widget:
build:
context: .
dockerfile: Dockerfile
container_name: 1millionbot-chat-widget
ports:
- "4200:4200"
volumes:
- .:/app
- /app/node_modules
environment:
- NODE_ENV=development
stdin_open: true
tty: true
46 changes: 46 additions & 0 deletions chat-widget/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"name": "chat-app",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test",
"docker:build": "docker-compose build",
"docker:start": "docker-compose up",
"docker:stop": "docker-compose stop",
"docker:down": "docker-compose down",
"docker:logs": "docker-compose logs -f"
},
"private": true,
"dependencies": {
"@angular/animations": "^16.2.0",
"@angular/common": "^16.2.0",
"@angular/compiler": "^16.2.0",
"@angular/core": "^16.2.0",
"@angular/forms": "^16.2.0",
"@angular/platform-browser": "^16.2.0",
"@angular/platform-browser-dynamic": "^16.2.0",
"@angular/router": "^16.2.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.13.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "^16.2.16",
"@angular/cli": "^16.2.16",
"@angular/compiler-cli": "^16.2.0",
"@types/jasmine": "~4.3.0",
"autoprefixer": "^10.4.21",
"jasmine-core": "~4.6.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"postcss": "^8.5.6",
"tailwindcss": "^3.4.18",
"typescript": "~5.1.3"
}
}
Loading