Compare commits
71 Commits
93fb15716c
...
playground
| Author | SHA1 | Date | |
|---|---|---|---|
| e22a1284de | |||
|
2a5de6df36
|
|||
|
7fa78e870b
|
|||
|
f17c0d08a1
|
|||
|
9eb293ccb1
|
|||
|
cedf771f16
|
|||
|
bb2e4f2a42
|
|||
|
1bd4ca0d98
|
|||
|
d04304b573
|
|||
|
e9fa0ea685
|
|||
|
45a0be4210
|
|||
|
f4df2431ad
|
|||
|
2bfec1640f
|
|||
|
281d35d63a
|
|||
| fcb35f1e40 | |||
| 048c2edb9b | |||
| f7096108f0 | |||
| de8aa503ba | |||
| d5ca679865 | |||
| 9362d110a5 | |||
| 2e94702ec2 | |||
| 709caf208a | |||
| 70fe066cdd | |||
| 73f456ed00 | |||
| c9431f62ef | |||
| b91f7fcfba | |||
| 58b25b0a44 | |||
| 01ed427b54 | |||
| fbaaf60d3f | |||
| 8fd37ec556 | |||
| 8f5ec53ac4 | |||
| a7833a9314 | |||
| 78399b5d1d | |||
| e387be003b | |||
| e69a89e5d3 | |||
| 8ef1febccc | |||
| 81d580ca99 | |||
| a845797b3d | |||
| 2366be8432 | |||
| b415164852 | |||
| c1b073c707 | |||
| b67b88e916 | |||
| df39029fd2 | |||
| 0b27ee4a9b | |||
| 87feefc139 | |||
| 4f23b2e657 | |||
| 0cf95d2cd4 | |||
| 9fcaef1068 | |||
| 51f4f7499c | |||
| 87d5b85c55 | |||
| 996b4097fd | |||
| f21238b61e | |||
| 80af0530c9 | |||
| 0e4e0ce224 | |||
| f67ab11ec7 | |||
| 8acdc92689 | |||
| 99259489ab | |||
| 396078240b | |||
| 4c67666c15 | |||
| df72c33705 | |||
| 164b1e5197 | |||
| 7d5bf0f7ee | |||
| 6e756e0792 | |||
| ca376559db | |||
| 94078ad1d5 | |||
| 57f42584de | |||
| e3a9e4badc | |||
| 263aae54b4 | |||
| 50c7ba87ef | |||
| 33cf657c70 | |||
| 30518e56d4 |
43
.devcontainer/Dockerfile
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
FROM node:14-bullseye
|
||||||
|
|
||||||
|
# Install additional tools including Ionic CLI and build dependencies for older projects
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
git \
|
||||||
|
curl \
|
||||||
|
vim \
|
||||||
|
python2.7 \
|
||||||
|
python2.7-dev \
|
||||||
|
build-essential \
|
||||||
|
&& ln -sf /usr/bin/python2.7 /usr/bin/python \
|
||||||
|
&& ln -sf /usr/bin/python2.7 /usr/bin/python2 \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install MongoDB shell via npm (works on all architectures)
|
||||||
|
# Note: mongosh requires Node 18+, but we're using Node 14 for legacy compatibility
|
||||||
|
# We'll use the mongo command from the mongo container instead
|
||||||
|
|
||||||
|
# Install global npm packages for development
|
||||||
|
RUN npm install -g \
|
||||||
|
@ionic/cli \
|
||||||
|
@angular/cli \
|
||||||
|
nodemon \
|
||||||
|
concurrently \
|
||||||
|
eslint \
|
||||||
|
prettier
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /workspaces/PfosiLooking-monorepo
|
||||||
|
|
||||||
|
# Copy package files for dependency caching
|
||||||
|
COPY package*.json ./
|
||||||
|
COPY app/package*.json ./app/
|
||||||
|
COPY backend/package*.json ./backend/
|
||||||
|
|
||||||
|
# Install dependencies separately to avoid node-sass issues during build
|
||||||
|
# We'll install them after container starts
|
||||||
|
# RUN npm install
|
||||||
|
# RUN cd app && npm install
|
||||||
|
# RUN cd backend && npm install
|
||||||
|
|
||||||
|
# Keep container running
|
||||||
|
CMD ["sleep", "infinity"]
|
||||||
764
.devcontainer/README.md
Normal file
@@ -0,0 +1,764 @@
|
|||||||
|
# Looking - DevContainer Configuration
|
||||||
|
|
||||||
|
> **VS Code Development Container setup for the Looking monorepo**
|
||||||
|
|
||||||
|
This directory contains the complete Docker-based development environment configuration for the Looking project. The DevContainer provides a consistent, isolated development environment with all necessary tools and dependencies pre-installed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Table of Contents
|
||||||
|
|
||||||
|
- [Overview](#overview)
|
||||||
|
- [What's Included](#whats-included)
|
||||||
|
- [Architecture](#architecture)
|
||||||
|
- [Services](#services)
|
||||||
|
- [Port Mappings](#port-mappings)
|
||||||
|
- [Volume Mounts](#volume-mounts)
|
||||||
|
- [Installation Process](#installation-process)
|
||||||
|
- [VS Code Integration](#vs-code-integration)
|
||||||
|
- [Environment Variables](#environment-variables)
|
||||||
|
- [Troubleshooting](#troubleshooting)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Overview
|
||||||
|
|
||||||
|
The DevContainer setup provides:
|
||||||
|
|
||||||
|
- **Consistent Environment**: Same Node 14, Python 2.7, and build tools across all developers
|
||||||
|
- **Isolated Dependencies**: No conflicts with your local system packages
|
||||||
|
- **Auto-Configuration**: Automatic dependency installation and VS Code extensions
|
||||||
|
- **Multi-Service Stack**: Frontend, backend, database, and admin UI all running together
|
||||||
|
- **Volume Optimization**: Fast performance with node_modules isolation
|
||||||
|
|
||||||
|
**Technologies**: Docker Compose, VS Code Remote - Containers, Node 14, MongoDB 4.4
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 What's Included
|
||||||
|
|
||||||
|
### Base Container
|
||||||
|
|
||||||
|
**Image**: Custom Dockerfile based on `node:14-bullseye`
|
||||||
|
|
||||||
|
**System Packages**:
|
||||||
|
|
||||||
|
- Git (version control)
|
||||||
|
- curl & wget (HTTP clients)
|
||||||
|
- vim (text editor)
|
||||||
|
- **Python 2.7** (required for legacy node-sass)
|
||||||
|
- build-essential (C++ compiler for native modules)
|
||||||
|
|
||||||
|
**Global NPM Packages**:
|
||||||
|
|
||||||
|
- `@ionic/cli` - Ionic framework CLI
|
||||||
|
- `@angular/cli` - Angular CLI (though project uses ionic-app-scripts)
|
||||||
|
- `nodemon` - Auto-restart development server
|
||||||
|
- `concurrently` - Run multiple commands in parallel
|
||||||
|
- `eslint` - JavaScript linting
|
||||||
|
- `prettier` - Code formatting
|
||||||
|
|
||||||
|
### VS Code Extensions (Auto-Installed)
|
||||||
|
|
||||||
|
| Extension | Purpose |
|
||||||
|
| -------------------------------------- | --------------------------- |
|
||||||
|
| **ms-vscode.vscode-json** | JSON language support |
|
||||||
|
| **bradlc.vscode-tailwindcss** | Tailwind CSS IntelliSense |
|
||||||
|
| **esbenp.prettier-vscode** | Code formatting |
|
||||||
|
| **dbaeumer.vscode-eslint** | JavaScript linting |
|
||||||
|
| **ms-vscode.vscode-typescript-next** | TypeScript support |
|
||||||
|
| **formulahendry.auto-rename-tag** | HTML/XML tag rename |
|
||||||
|
| **christian-kohler.path-intellisense** | Autocomplete file paths |
|
||||||
|
| **mongodb.mongodb-vscode** | MongoDB database management |
|
||||||
|
|
||||||
|
### Editor Settings (Auto-Configured)
|
||||||
|
|
||||||
|
- **Format on Save**: Enabled
|
||||||
|
- **Default Formatter**: Prettier
|
||||||
|
- **ESLint Auto-Fix**: Runs on save
|
||||||
|
- **Emmet**: Enabled for JavaScript (React/JSX)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🏗️ Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ DevContainer Stack │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ App Container (node:14-bullseye) │ │
|
||||||
|
│ │ - VS Code Remote Server │ │
|
||||||
|
│ │ - Node.js 14.x + npm │ │
|
||||||
|
│ │ - Python 2.7 (for node-sass) │ │
|
||||||
|
│ │ - Ionic/Angular Dev Server (port 8100) │ │
|
||||||
|
│ │ - Express API Server (port 3069) │ │
|
||||||
|
│ │ - Development tools (nodemon, eslint, prettier) │ │
|
||||||
|
│ └──────────────────────────────────────────────────────┘ │
|
||||||
|
│ ↓↑ │
|
||||||
|
│ ┌──────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ MongoDB 4.4 (mongo:4.4) │ │
|
||||||
|
│ │ - Database: urge │ │
|
||||||
|
│ │ - Port: 27017 │ │
|
||||||
|
│ │ - Credentials: admin/password │ │
|
||||||
|
│ │ - Health Check: ping command every 30s │ │
|
||||||
|
│ │ - Seed Data: /docker-entrypoint-initdb.d │ │
|
||||||
|
│ └──────────────────────────────────────────────────────┘ │
|
||||||
|
│ ↓↑ │
|
||||||
|
│ ┌──────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Mongo Express (mongo-express:latest) │ │
|
||||||
|
│ │ - Web-based MongoDB admin UI │ │
|
||||||
|
│ │ - Port: 8081 │ │
|
||||||
|
│ │ - Login: admin/password │ │
|
||||||
|
│ │ - Browse collections, run queries │ │
|
||||||
|
│ └──────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ Network: dev-network (bridge driver) │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
↓ ↓ ↓
|
||||||
|
Port 8100 Port 3069 Port 27017
|
||||||
|
(Frontend Dev) (Backend API) (MongoDB)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔌 Services
|
||||||
|
|
||||||
|
### 1. App Service (Development Container)
|
||||||
|
|
||||||
|
**File**: [docker-compose.yml](docker-compose.yml)
|
||||||
|
|
||||||
|
**Configuration**:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
service: app
|
||||||
|
build:
|
||||||
|
context: ..
|
||||||
|
dockerfile: .devcontainer/Dockerfile
|
||||||
|
command: sleep infinity
|
||||||
|
volumes:
|
||||||
|
- ../..:/workspaces:cached
|
||||||
|
- /workspaces/PfosiLooking-monorepo/app/node_modules
|
||||||
|
- /workspaces/PfosiLooking-monorepo/backend/node_modules
|
||||||
|
depends_on:
|
||||||
|
mongo:
|
||||||
|
condition: service_healthy
|
||||||
|
```
|
||||||
|
|
||||||
|
**Purpose**:
|
||||||
|
|
||||||
|
- Main development container where you write code
|
||||||
|
- Runs VS Code Remote Server
|
||||||
|
- Hosts Ionic dev server (port 8100) and Express API (port 3069)
|
||||||
|
- Waits for MongoDB health check before starting
|
||||||
|
|
||||||
|
**Command**: `sleep infinity` keeps container running indefinitely
|
||||||
|
|
||||||
|
**Node Modules Isolation**:
|
||||||
|
|
||||||
|
- Anonymous volumes for `app/node_modules` and `backend/node_modules`
|
||||||
|
- Prevents host filesystem performance issues (especially on Mac/Windows)
|
||||||
|
- Each workspace has its own isolated dependencies
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. MongoDB Service
|
||||||
|
|
||||||
|
**Configuration**:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
service: mongo
|
||||||
|
image: mongo:4.4
|
||||||
|
environment:
|
||||||
|
- MONGO_INITDB_DATABASE=urge
|
||||||
|
- MONGO_INITDB_ROOT_USERNAME=admin
|
||||||
|
- MONGO_INITDB_ROOT_PASSWORD=password
|
||||||
|
ports:
|
||||||
|
- "27017:27017"
|
||||||
|
volumes:
|
||||||
|
- mongo_data:/data/db
|
||||||
|
- ../backend/data:/docker-entrypoint-initdb.d:ro
|
||||||
|
healthcheck:
|
||||||
|
test: echo 'db.runCommand("ping").ok' | mongo localhost:27017/urge --quiet
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 40s
|
||||||
|
```
|
||||||
|
|
||||||
|
**Purpose**:
|
||||||
|
|
||||||
|
- NoSQL database for profiles, users, and messages
|
||||||
|
- Persistent storage via `mongo_data` volume
|
||||||
|
- Auto-loads seed data from `backend/data/` on first run
|
||||||
|
|
||||||
|
**Credentials**:
|
||||||
|
|
||||||
|
- **Admin User**: `admin`
|
||||||
|
- **Admin Password**: `password`
|
||||||
|
- **Database**: `urge`
|
||||||
|
|
||||||
|
**Health Check**:
|
||||||
|
|
||||||
|
- Runs every 30 seconds
|
||||||
|
- Pings database to verify it's responsive
|
||||||
|
- App container waits for healthy status before starting
|
||||||
|
|
||||||
|
**Seed Data**:
|
||||||
|
|
||||||
|
- Files in `backend/data/` are mounted to `/docker-entrypoint-initdb.d`
|
||||||
|
- MongoDB automatically executes `.js` and `.sh` files in that directory
|
||||||
|
- Used for initial database population (if scripts exist)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Mongo Express Service
|
||||||
|
|
||||||
|
**Configuration**:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
service: mongo-express
|
||||||
|
image: mongo-express:latest
|
||||||
|
environment:
|
||||||
|
- ME_CONFIG_MONGODB_SERVER=mongo
|
||||||
|
- ME_CONFIG_MONGODB_PORT=27017
|
||||||
|
- ME_CONFIG_MONGODB_ADMINUSERNAME=admin
|
||||||
|
- ME_CONFIG_MONGODB_ADMINPASSWORD=password
|
||||||
|
- ME_CONFIG_BASICAUTH_USERNAME=admin
|
||||||
|
- ME_CONFIG_BASICAUTH_PASSWORD=password
|
||||||
|
ports:
|
||||||
|
- "8081:8081"
|
||||||
|
depends_on:
|
||||||
|
- mongo
|
||||||
|
```
|
||||||
|
|
||||||
|
**Purpose**:
|
||||||
|
|
||||||
|
- Web-based database administration tool
|
||||||
|
- View/edit collections without command-line mongo shell
|
||||||
|
- Useful for debugging data issues
|
||||||
|
|
||||||
|
**Access**: http://localhost:8081
|
||||||
|
|
||||||
|
- **Login**: admin / password
|
||||||
|
|
||||||
|
**Features**:
|
||||||
|
|
||||||
|
- Browse collections visually
|
||||||
|
- Run MongoDB queries
|
||||||
|
- Create/edit/delete documents
|
||||||
|
- View indexes and stats
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔌 Port Mappings
|
||||||
|
|
||||||
|
| Port | Service | Purpose | Access |
|
||||||
|
| --------- | ---------------- | -------------------- | ------------------------- |
|
||||||
|
| **8100** | Ionic Dev Server | Frontend application | http://localhost:8100 |
|
||||||
|
| **3069** | Express API | Backend REST API | http://localhost:3069 |
|
||||||
|
| **27017** | MongoDB | Database server | mongodb://localhost:27017 |
|
||||||
|
| **8081** | Mongo Express | Database admin UI | http://localhost:8081 |
|
||||||
|
|
||||||
|
### Port Forwarding Configuration
|
||||||
|
|
||||||
|
**File**: [devcontainer.json](devcontainer.json)
|
||||||
|
|
||||||
|
```json
|
||||||
|
"forwardPorts": [8100, 3069, 27017, 8081],
|
||||||
|
"portsAttributes": {
|
||||||
|
"8100": {
|
||||||
|
"label": "Frontend (Ionic)",
|
||||||
|
"onAutoForward": "notify"
|
||||||
|
},
|
||||||
|
"3069": {
|
||||||
|
"label": "Backend API",
|
||||||
|
"onAutoForward": "notify"
|
||||||
|
},
|
||||||
|
"27017": {
|
||||||
|
"label": "MongoDB",
|
||||||
|
"onAutoForward": "silent"
|
||||||
|
},
|
||||||
|
"8081": {
|
||||||
|
"label": "Mongo Express",
|
||||||
|
"onAutoForward": "notify"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Notifications**:
|
||||||
|
|
||||||
|
- **notify**: VS Code shows notification when port is forwarded
|
||||||
|
- **silent**: No notification (database port)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💾 Volume Mounts
|
||||||
|
|
||||||
|
### Workspace Volume
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
volumes:
|
||||||
|
- ../..:/workspaces:cached
|
||||||
|
```
|
||||||
|
|
||||||
|
**Purpose**:
|
||||||
|
|
||||||
|
- Mounts parent directory (entire monorepo) into container
|
||||||
|
- `:cached` flag optimizes for read-heavy operations
|
||||||
|
- Enables live code changes to reflect immediately
|
||||||
|
|
||||||
|
**Path Inside Container**: `/workspaces/PfosiLooking-monorepo/`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Node Modules Isolation
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
volumes:
|
||||||
|
- /workspaces/PfosiLooking-monorepo/app/node_modules
|
||||||
|
- /workspaces/PfosiLooking-monorepo/backend/node_modules
|
||||||
|
```
|
||||||
|
|
||||||
|
**Purpose**:
|
||||||
|
|
||||||
|
- Creates anonymous Docker volumes for node_modules directories
|
||||||
|
- Isolates dependencies from host filesystem
|
||||||
|
- **Solves performance issues** on Mac/Windows (Docker Desktop)
|
||||||
|
|
||||||
|
**Why?**:
|
||||||
|
|
||||||
|
- node_modules can have 100,000+ files
|
||||||
|
- Host filesystem sync is slow for that many files
|
||||||
|
- Volume isolation provides native container filesystem performance
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### MongoDB Data Volume
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
volumes: mongo_data:/data/db
|
||||||
|
```
|
||||||
|
|
||||||
|
**Purpose**:
|
||||||
|
|
||||||
|
- Persistent storage for MongoDB data
|
||||||
|
- Survives container restarts
|
||||||
|
- Named volume managed by Docker
|
||||||
|
|
||||||
|
**Location**: Docker volume `mongo_data` (not visible in project directory)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Seed Data Volume
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
volumes:
|
||||||
|
- ../backend/data:/docker-entrypoint-initdb.d:ro
|
||||||
|
```
|
||||||
|
|
||||||
|
**Purpose**:
|
||||||
|
|
||||||
|
- Mounts seed data directory into MongoDB initialization location
|
||||||
|
- `:ro` = read-only (MongoDB can't modify host files)
|
||||||
|
- Scripts run automatically on first database creation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 Installation Process
|
||||||
|
|
||||||
|
### Automatic Setup (postCreateCommand)
|
||||||
|
|
||||||
|
**File**: [postCreateCommand.sh](postCreateCommand.sh)
|
||||||
|
|
||||||
|
The script runs automatically after the container is created:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
set -e # Exit on any error
|
||||||
|
|
||||||
|
# 1. Install root dependencies
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# 2. Install backend dependencies
|
||||||
|
cd backend && npm install
|
||||||
|
|
||||||
|
# 3. Install app dependencies with legacy support
|
||||||
|
cd app && npm install --legacy-peer-deps
|
||||||
|
```
|
||||||
|
|
||||||
|
**Execution Time**: 5-10 minutes (first time only)
|
||||||
|
|
||||||
|
**What Happens**:
|
||||||
|
|
||||||
|
1. **Root workspace** (`package.json`):
|
||||||
|
- Installs `concurrently`, `npm-run-all`
|
||||||
|
- Enables `npm run dev:all` script
|
||||||
|
2. **Backend** (`backend/package.json`):
|
||||||
|
- Installs Express, Mongoose, JWT, etc.
|
||||||
|
- Standard npm install (no special flags)
|
||||||
|
3. **Frontend** (`app/package.json`):
|
||||||
|
- Installs Ionic 3, Angular 5, TypeScript 2.4
|
||||||
|
- **Uses `--legacy-peer-deps`** flag
|
||||||
|
- Required for old Ionic 3 packages with npm 7+
|
||||||
|
- Falls back to `--force` if legacy deps fail
|
||||||
|
|
||||||
|
**Progress Visibility**:
|
||||||
|
|
||||||
|
- VS Code shows "Running postCreateCommand" notification
|
||||||
|
- View detailed output: Terminal → "postCreateCommand" tab
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Manual Installation
|
||||||
|
|
||||||
|
If automatic setup fails:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Open VS Code terminal inside DevContainer
|
||||||
|
# Terminal → New Terminal (or Ctrl + `)
|
||||||
|
|
||||||
|
# 2. Install dependencies manually
|
||||||
|
npm install
|
||||||
|
cd backend && npm install && cd ..
|
||||||
|
cd app && npm install --legacy-peer-deps && cd ..
|
||||||
|
|
||||||
|
# 3. Verify installation
|
||||||
|
npm run dev:all
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎨 VS Code Integration
|
||||||
|
|
||||||
|
### DevContainer Configuration
|
||||||
|
|
||||||
|
**File**: [devcontainer.json](devcontainer.json)
|
||||||
|
|
||||||
|
**Key Settings**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "PfosiLooking Full Stack",
|
||||||
|
"dockerComposeFile": "docker-compose.yml",
|
||||||
|
"service": "app",
|
||||||
|
"workspaceFolder": "/workspaces/PfosiLooking-monorepo",
|
||||||
|
"shutdownAction": "stopCompose"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Shutdown Behavior**:
|
||||||
|
|
||||||
|
- `"stopCompose"` - Stops all services when closing VS Code
|
||||||
|
- Preserves data in volumes (MongoDB data, node_modules)
|
||||||
|
- Fast restart on next open (no rebuild needed)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
**Auto-Installed**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ghcr.io/devcontainers/features/node:1": {
|
||||||
|
"version": "14"
|
||||||
|
},
|
||||||
|
"ghcr.io/devcontainers/features/git:1": {},
|
||||||
|
"ghcr.io/devcontainers/features/github-cli:1": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Provides**:
|
||||||
|
|
||||||
|
- Node.js 14.x (managed version)
|
||||||
|
- Git (latest stable)
|
||||||
|
- GitHub CLI (`gh` command)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Post-Start Message
|
||||||
|
|
||||||
|
```json
|
||||||
|
"postStartCommand": "echo 'DevContainer ready! Run npm run dev:all to start development.'"
|
||||||
|
```
|
||||||
|
|
||||||
|
Displays helpful message after container starts.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔐 Environment Variables
|
||||||
|
|
||||||
|
### Development Environment File
|
||||||
|
|
||||||
|
**File**: [dev.env](dev.env)
|
||||||
|
|
||||||
|
**Contains** (example values):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
NODE_ENV=development
|
||||||
|
MONGODB_URI=mongodb://mongo:27017/urge
|
||||||
|
REACT_APP_API_URL=http://localhost:3069
|
||||||
|
PORT=3069
|
||||||
|
IONIC_PORT=8100
|
||||||
|
JWT_SECRET=dev-secret-change-in-production
|
||||||
|
```
|
||||||
|
|
||||||
|
**Usage**:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
env_file:
|
||||||
|
- dev.env
|
||||||
|
```
|
||||||
|
|
||||||
|
⚠️ **Security Note**:
|
||||||
|
|
||||||
|
- `dev.env` may contain secrets
|
||||||
|
- DO NOT commit real credentials
|
||||||
|
- Use `dev.env.example` for templates
|
||||||
|
- Add `dev.env` to `.gitignore`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Inline Environment Variables
|
||||||
|
|
||||||
|
**In docker-compose.yml**:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=development
|
||||||
|
- MONGODB_URI=mongodb://mongo:27017/urge
|
||||||
|
- PORT=3069
|
||||||
|
```
|
||||||
|
|
||||||
|
These override values from `dev.env` if both are present.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Troubleshooting
|
||||||
|
|
||||||
|
### Container Won't Start
|
||||||
|
|
||||||
|
**Problem**: DevContainer fails to start or build
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
|
||||||
|
1. **Rebuild without cache**:
|
||||||
|
|
||||||
|
```
|
||||||
|
F1 → "Dev Containers: Rebuild Container Without Cache"
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Check Docker Desktop is running**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker ps
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Free up disk space**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker system prune -a
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **View build logs**:
|
||||||
|
```
|
||||||
|
F1 → "Dev Containers: Show Container Log"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Python 2.7 Errors
|
||||||
|
|
||||||
|
**Problem**: `node-sass` or `node-gyp` errors about Python
|
||||||
|
|
||||||
|
**Check Python symlinks**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Inside container
|
||||||
|
which python
|
||||||
|
python --version # Should show Python 2.7.x
|
||||||
|
```
|
||||||
|
|
||||||
|
**Dockerfile installs**:
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
RUN apt-get install -y python2.7 python2.7-dev \
|
||||||
|
&& ln -sf /usr/bin/python2.7 /usr/bin/python
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why?**: node-sass (used by Ionic 3) requires Python 2.7 to build native modules.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### MongoDB Connection Failed
|
||||||
|
|
||||||
|
**Problem**: Backend can't connect to MongoDB
|
||||||
|
|
||||||
|
**1. Check MongoDB is healthy**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker ps
|
||||||
|
# Look for "healthy" status on mongo container
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. Test connection manually**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec -it <mongo-container-id> mongo --eval "db.runCommand('ping')"
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. Verify connection string**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo $MONGODB_URI
|
||||||
|
# Should be: mongodb://mongo:27017/urge
|
||||||
|
```
|
||||||
|
|
||||||
|
**4. Wait for health check**:
|
||||||
|
|
||||||
|
- Health check runs every 30s
|
||||||
|
- Container may need 40s start period
|
||||||
|
- App service waits for `service_healthy` condition
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Port Already in Use
|
||||||
|
|
||||||
|
**Problem**: Port 8100, 3069, or 27017 already in use on host
|
||||||
|
|
||||||
|
**Find conflicting process**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Mac/Linux
|
||||||
|
lsof -i :8100
|
||||||
|
lsof -i :3069
|
||||||
|
lsof -i :27017
|
||||||
|
|
||||||
|
# Kill process
|
||||||
|
kill -9 <PID>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Or change ports** in [docker-compose.yml](docker-compose.yml):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
ports:
|
||||||
|
- "8101:8100" # Changed from 8100
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Node Modules Issues
|
||||||
|
|
||||||
|
**Problem**: Module not found, peer dependency warnings, or version conflicts
|
||||||
|
|
||||||
|
**Solution 1 - Reinstall**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Remove all node_modules
|
||||||
|
rm -rf node_modules app/node_modules backend/node_modules
|
||||||
|
|
||||||
|
# Reinstall
|
||||||
|
npm run install:all
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solution 2 - Clear volume**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Exit container, then from host
|
||||||
|
docker-compose -f .devcontainer/docker-compose.yml down -v
|
||||||
|
# This deletes ALL volumes including node_modules and MongoDB data
|
||||||
|
|
||||||
|
# Rebuild container
|
||||||
|
F1 → "Dev Containers: Rebuild Container"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Slow Performance
|
||||||
|
|
||||||
|
**Problem**: File watching or compilation is slow
|
||||||
|
|
||||||
|
**Causes**:
|
||||||
|
|
||||||
|
- Docker Desktop on Mac/Windows has filesystem sync overhead
|
||||||
|
- Large node_modules directories
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
|
||||||
|
1. **Verify node_modules isolation**:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# These volumes should exist in docker-compose.yml
|
||||||
|
- /workspaces/PfosiLooking-monorepo/app/node_modules
|
||||||
|
- /workspaces/PfosiLooking-monorepo/backend/node_modules
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Increase Docker resources**:
|
||||||
|
|
||||||
|
- Docker Desktop → Settings → Resources
|
||||||
|
- Increase CPU: 4+ cores
|
||||||
|
- Increase Memory: 8+ GB
|
||||||
|
|
||||||
|
3. **Use `:cached` mount flag** (already configured):
|
||||||
|
```yaml
|
||||||
|
- ../..:/workspaces:cached
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Database Permissions
|
||||||
|
|
||||||
|
**Problem**: Can't write to MongoDB or access Mongo Express
|
||||||
|
|
||||||
|
**Check credentials**:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# MongoDB
|
||||||
|
MONGO_INITDB_ROOT_USERNAME=admin
|
||||||
|
MONGO_INITDB_ROOT_PASSWORD=password
|
||||||
|
|
||||||
|
# Mongo Express
|
||||||
|
ME_CONFIG_MONGODB_ADMINUSERNAME=admin
|
||||||
|
ME_CONFIG_MONGODB_ADMINPASSWORD=password
|
||||||
|
ME_CONFIG_BASICAUTH_USERNAME=admin
|
||||||
|
ME_CONFIG_BASICAUTH_PASSWORD=password
|
||||||
|
```
|
||||||
|
|
||||||
|
**Access Mongo Express**:
|
||||||
|
|
||||||
|
- URL: http://localhost:8081
|
||||||
|
- Login: admin / password (HTTP Basic Auth)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### VS Code Extensions Not Installing
|
||||||
|
|
||||||
|
**Problem**: Extensions listed in devcontainer.json don't install
|
||||||
|
|
||||||
|
**Manual install**:
|
||||||
|
|
||||||
|
```
|
||||||
|
F1 → "Extensions: Install Extensions"
|
||||||
|
Search for extension ID (e.g., "esbenp.prettier-vscode")
|
||||||
|
```
|
||||||
|
|
||||||
|
**Rebuild to retry auto-install**:
|
||||||
|
|
||||||
|
```
|
||||||
|
F1 → "Dev Containers: Rebuild Container"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 Related Documentation
|
||||||
|
|
||||||
|
- **[Root README](../README.md)** - Project overview and quick start
|
||||||
|
- **[Backend README](../backend/README.md)** - API server setup
|
||||||
|
- **[Frontend README](../app/README.md)** - Ionic app setup
|
||||||
|
- **[Deployment Guide](../DEPLOYMENT.md)** - Production deployment
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Need Help?** Check the [root troubleshooting section](../README.md#troubleshooting) for additional solutions.
|
||||||
18
.devcontainer/dev.env
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
NODE_ENV=development
|
||||||
|
MONGODB_URI=mongodb://admin:password@localhost:27017/urge?authSource=admin
|
||||||
|
JWT_SECRET=your-dev-jwt-secret-here
|
||||||
|
PORT=3069
|
||||||
|
REACT_APP_API_URL=http://localhost:3069
|
||||||
|
|
||||||
|
# MongoDB
|
||||||
|
MONGO_ROOT_USER=admin
|
||||||
|
MONGO_ROOT_PASSWORD=password
|
||||||
|
MONGO_EXPRESS_USER=admin
|
||||||
|
MONGO_EXPRESS_PASSWORD=password
|
||||||
|
|
||||||
|
# Optional API keys for development
|
||||||
|
GOOGLE_MAPS_API_KEY=your-google-maps-api-key
|
||||||
|
MAIL_HOST=smtp.gmail.com
|
||||||
|
MAIL_PORT=587
|
||||||
|
MAIL_USER=your-email@gmail.com
|
||||||
|
MAIL_PASS=your-email-password
|
||||||
60
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
{
|
||||||
|
"name": "PfosiLooking Full Stack",
|
||||||
|
"dockerComposeFile": "docker-compose.yml",
|
||||||
|
"service": "app",
|
||||||
|
"workspaceFolder": "/workspaces/PfosiLooking-monorepo",
|
||||||
|
"shutdownAction": "stopCompose",
|
||||||
|
"features": {
|
||||||
|
"ghcr.io/devcontainers/features/node:1": {
|
||||||
|
"version": "14"
|
||||||
|
},
|
||||||
|
"ghcr.io/devcontainers/features/git:1": {},
|
||||||
|
"ghcr.io/devcontainers/features/github-cli:1": {}
|
||||||
|
},
|
||||||
|
"customizations": {
|
||||||
|
"vscode": {
|
||||||
|
"extensions": [
|
||||||
|
"ms-vscode.vscode-json",
|
||||||
|
"bradlc.vscode-tailwindcss",
|
||||||
|
"esbenp.prettier-vscode",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"ms-vscode.vscode-typescript-next",
|
||||||
|
"formulahendry.auto-rename-tag",
|
||||||
|
"christian-kohler.path-intellisense",
|
||||||
|
"ms-vscode.vscode-json",
|
||||||
|
"mongodb.mongodb-vscode"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll.eslint": "explicit"
|
||||||
|
},
|
||||||
|
"emmet.includeLanguages": {
|
||||||
|
"javascript": "javascriptreact"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"forwardPorts": [8100, 3069, 27017, 8081],
|
||||||
|
"portsAttributes": {
|
||||||
|
"8100": {
|
||||||
|
"label": "Frontend (Ionic)",
|
||||||
|
"onAutoForward": "notify"
|
||||||
|
},
|
||||||
|
"3069": {
|
||||||
|
"label": "Backend API",
|
||||||
|
"onAutoForward": "notify"
|
||||||
|
},
|
||||||
|
"27017": {
|
||||||
|
"label": "MongoDB",
|
||||||
|
"onAutoForward": "silent"
|
||||||
|
},
|
||||||
|
"8081": {
|
||||||
|
"label": "Mongo Express",
|
||||||
|
"onAutoForward": "notify"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"postCreateCommand": ".devcontainer/postCreateCommand.sh",
|
||||||
|
"postStartCommand": "echo 'DevContainer ready! Run npm run dev:all to start development.'"
|
||||||
|
}
|
||||||
70
.devcontainer/docker-compose.yml
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
version: "3.8"
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: ..
|
||||||
|
dockerfile: .devcontainer/Dockerfile
|
||||||
|
volumes:
|
||||||
|
- ../..:/workspaces:cached
|
||||||
|
- /workspaces/PfosiLooking-monorepo/app/node_modules
|
||||||
|
- /workspaces/PfosiLooking-monorepo/backend/node_modules
|
||||||
|
command: sleep infinity
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=development
|
||||||
|
- MONGODB_URI=mongodb://mongo:27017/urge
|
||||||
|
- REACT_APP_API_URL=http://localhost:3069
|
||||||
|
- PORT=3069
|
||||||
|
- IONIC_PORT=8100
|
||||||
|
env_file:
|
||||||
|
- dev.env
|
||||||
|
networks:
|
||||||
|
- dev-network
|
||||||
|
depends_on:
|
||||||
|
mongo:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
mongo:
|
||||||
|
image: mongo:4.4
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- MONGO_INITDB_DATABASE=urge
|
||||||
|
- MONGO_INITDB_ROOT_USERNAME=admin
|
||||||
|
- MONGO_INITDB_ROOT_PASSWORD=password
|
||||||
|
ports:
|
||||||
|
- "27017:27017"
|
||||||
|
networks:
|
||||||
|
- dev-network
|
||||||
|
volumes:
|
||||||
|
- mongo_data:/data/db
|
||||||
|
- ../backend/data:/docker-entrypoint-initdb.d:ro
|
||||||
|
healthcheck:
|
||||||
|
test: echo 'db.runCommand("ping").ok' | mongo localhost:27017/urge --quiet
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 40s
|
||||||
|
|
||||||
|
mongo-express:
|
||||||
|
image: mongo-express:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- ME_CONFIG_MONGODB_SERVER=mongo
|
||||||
|
- ME_CONFIG_MONGODB_PORT=27017
|
||||||
|
- ME_CONFIG_MONGODB_ADMINUSERNAME=admin
|
||||||
|
- ME_CONFIG_MONGODB_ADMINPASSWORD=password
|
||||||
|
- ME_CONFIG_BASICAUTH_USERNAME=admin
|
||||||
|
- ME_CONFIG_BASICAUTH_PASSWORD=password
|
||||||
|
ports:
|
||||||
|
- "8081:8081"
|
||||||
|
networks:
|
||||||
|
- dev-network
|
||||||
|
depends_on:
|
||||||
|
- mongo
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mongo_data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
dev-network:
|
||||||
|
driver: bridge
|
||||||
31
.devcontainer/postCreateCommand.sh
Executable file
@@ -0,0 +1,31 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "Setting up PfosiLooking monorepo dependencies..."
|
||||||
|
echo "Using Node version: $(node --version)"
|
||||||
|
|
||||||
|
# Install root dependencies first
|
||||||
|
echo "Installing root dependencies..."
|
||||||
|
npm install
|
||||||
|
|
||||||
|
echo "Installing backend dependencies..."
|
||||||
|
cd backend && npm install
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
echo "Installing app dependencies (Ionic 3 with legacy packages)..."
|
||||||
|
cd app
|
||||||
|
|
||||||
|
# For Node 14 and older Ionic 3 projects, we need special handling
|
||||||
|
echo "Installing with legacy peer dependencies support..."
|
||||||
|
if npm install --legacy-peer-deps; then
|
||||||
|
echo "App dependencies installed successfully!"
|
||||||
|
else
|
||||||
|
echo "Standard install failed, trying alternative approach..."
|
||||||
|
# Try with force flag as last resort for very old packages
|
||||||
|
npm install --legacy-peer-deps --force
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
echo "All dependencies installed successfully!"
|
||||||
|
echo "You can now start development with: npm run dev:all"
|
||||||
184
.drone.yml
@@ -1,184 +0,0 @@
|
|||||||
kind: pipeline
|
|
||||||
type: docker
|
|
||||||
name: Publish Pipeline
|
|
||||||
|
|
||||||
workspace:
|
|
||||||
path: /drone/looking
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Publish Image
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
auto_tag: true
|
|
||||||
repo: git.mifi.dev/mifi/pfosi-looking-api
|
|
||||||
registry: git.mifi.dev
|
|
||||||
build_args:
|
|
||||||
- MONGO_ENTRY_FILE=latest
|
|
||||||
- MONGO_VERSION=latest
|
|
||||||
- NPM_TOKEN:
|
|
||||||
from_secret: reg_token
|
|
||||||
ssh-agent-key:
|
|
||||||
from_secret: reg_token
|
|
||||||
username: <token>
|
|
||||||
password:
|
|
||||||
from_secret: reg_token
|
|
||||||
secrets: [reg_token]
|
|
||||||
- name: Report Image Publish Status
|
|
||||||
image: plugins/webhook
|
|
||||||
settings:
|
|
||||||
urls: https://lab.mifi.dev/hooks/ccw34hdf7tgbjmzp96nptn938r
|
|
||||||
content_type: application/json
|
|
||||||
template: |
|
|
||||||
{
|
|
||||||
"icon_url":"https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/120/apple/198/freezing-face_1f976.png",
|
|
||||||
"text": "[{{ repo.name }} - New docker image release {{tag}} from # {{ build.number }}] Deploy {{ build.status }} {{#success build.status}}:tada:{{else}}:poop:{{/success}}",
|
|
||||||
"username":"DroneBot"
|
|
||||||
}
|
|
||||||
when:
|
|
||||||
status:
|
|
||||||
- success
|
|
||||||
- failure
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
- name: dockerconfig
|
|
||||||
host:
|
|
||||||
path: /volume1/docker/dockerconfig.json
|
|
||||||
- name: dockersock
|
|
||||||
host:
|
|
||||||
path: /var/run/docker.sock
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
branch:
|
|
||||||
- master
|
|
||||||
- develop
|
|
||||||
event:
|
|
||||||
- push
|
|
||||||
|
|
||||||
# trigger:
|
|
||||||
# event:
|
|
||||||
# - tag
|
|
||||||
|
|
||||||
# ---
|
|
||||||
# kind: pipeline
|
|
||||||
# type: docker
|
|
||||||
# name: Staging Deploy Pipeline
|
|
||||||
|
|
||||||
# workspace:
|
|
||||||
# path: /drone/auth
|
|
||||||
|
|
||||||
# steps:
|
|
||||||
# - name: Deploy Container
|
|
||||||
# image: docker
|
|
||||||
# privileged: true
|
|
||||||
# environment:
|
|
||||||
# CONTAINER_PREFIX: staging
|
|
||||||
# HOST: area51.mifi.dev
|
|
||||||
# ROUTE_PREFIX: /auth
|
|
||||||
# PORT: 9001
|
|
||||||
# commands:
|
|
||||||
# - docker compose -f docker-compose.staging-build.yml build --pull --no-cache
|
|
||||||
# - docker compose -f docker-compose.staging-build.yml up --remove-orphans --force-recreate --wait
|
|
||||||
# volumes:
|
|
||||||
# - name: env-secrets
|
|
||||||
# path: /drone/auth/staging.env
|
|
||||||
# - name: dockersock
|
|
||||||
# path: /var/run/docker.sock
|
|
||||||
# - name: dockerconfig
|
|
||||||
# path: /drone/auth/.docker/config.json
|
|
||||||
# - name: Send Status Notifications
|
|
||||||
# image: plugins/webhook
|
|
||||||
# privileged: true
|
|
||||||
# settings:
|
|
||||||
# urls: https://lab.mifi.dev/hooks/ccw34hdf7tgbjmzp96nptn938r
|
|
||||||
# content_type: application/json
|
|
||||||
# template: |
|
|
||||||
# {
|
|
||||||
# "icon_url":"https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/120/apple/198/freezing-face_1f976.png",
|
|
||||||
# "text": "[{{ repo.name }} - Build # {{ build.number }}] Staging Deploy {{ build.status }} {{#success build.status}}:tada:{{else}}:poop:{{/success}}",
|
|
||||||
# "username":"DroneBot"
|
|
||||||
# }
|
|
||||||
# when:
|
|
||||||
# status:
|
|
||||||
# - success
|
|
||||||
# - failure
|
|
||||||
|
|
||||||
# volumes:
|
|
||||||
# - name: dockerconfig
|
|
||||||
# host:
|
|
||||||
# path: /volume1/docker/dockerconfig.json
|
|
||||||
# - name: dockersock
|
|
||||||
# host:
|
|
||||||
# path: /var/run/docker.sock
|
|
||||||
# - name: env-secrets
|
|
||||||
# host:
|
|
||||||
# path: /volume1/docker/beethoven/labs-auth/staging.env
|
|
||||||
|
|
||||||
# depends_on:
|
|
||||||
# - Test Pipeline
|
|
||||||
|
|
||||||
# trigger:
|
|
||||||
# branch:
|
|
||||||
# - develop
|
|
||||||
# event:
|
|
||||||
# - push
|
|
||||||
|
|
||||||
# ---
|
|
||||||
# kind: pipeline
|
|
||||||
# type: docker
|
|
||||||
# name: Production Deploy Pipeline
|
|
||||||
|
|
||||||
# workspace:
|
|
||||||
# path: /drone/looking
|
|
||||||
|
|
||||||
# clone:
|
|
||||||
# disable: true
|
|
||||||
|
|
||||||
# steps:
|
|
||||||
# - name: Deploy Container
|
|
||||||
# image: plugins/webhook
|
|
||||||
# settings:
|
|
||||||
# # urls: https://portainer.mifi.dev/api/stacks/webhooks/968d2244-2548-4f0b-8c18-bbc9bc35305d
|
|
||||||
# - name: Send Status Notifications
|
|
||||||
# image: plugins/webhook
|
|
||||||
# privileged: true
|
|
||||||
# settings:
|
|
||||||
# urls: https://lab.mifi.dev/hooks/ccw34hdf7tgbjmzp96nptn938r
|
|
||||||
# content_type: application/json
|
|
||||||
# template: |
|
|
||||||
# {
|
|
||||||
# "icon_url":"https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/120/apple/198/freezing-face_1f976.png",
|
|
||||||
# "text": "[{{ repo.name }} - Build # {{ build.number }}] Production Deploy {{ build.status }} {{#success build.status}}:tada:{{else}}:poop:{{/success}}",
|
|
||||||
# "username":"DroneBot"
|
|
||||||
# }
|
|
||||||
# when:
|
|
||||||
# status:
|
|
||||||
# - success
|
|
||||||
# - failure
|
|
||||||
|
|
||||||
# depends_on:
|
|
||||||
# - Publish Pipeline
|
|
||||||
|
|
||||||
# trigger:
|
|
||||||
# event:
|
|
||||||
# - promote
|
|
||||||
# target:
|
|
||||||
# - production
|
|
||||||
|
|
||||||
# kind: pipeline
|
|
||||||
# type: docker
|
|
||||||
# name: Deploy to miCloud
|
|
||||||
|
|
||||||
# steps:
|
|
||||||
# - name: deploy-dev
|
|
||||||
# image: node:latest
|
|
||||||
# commands:
|
|
||||||
# - npm install -g forever
|
|
||||||
# - npm install
|
|
||||||
# - forever start bin/www
|
|
||||||
|
|
||||||
# trigger:
|
|
||||||
# branch:
|
|
||||||
# - master
|
|
||||||
# event:
|
|
||||||
# - push
|
|
||||||
|
|
||||||
55
.gitignore
vendored
@@ -1 +1,54 @@
|
|||||||
.vscode/settings.json
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
*/node_modules/
|
||||||
|
|
||||||
|
# Production builds
|
||||||
|
/app/www/
|
||||||
|
/app/dist/
|
||||||
|
/backend/dist/
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Environment files
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Cache
|
||||||
|
.cache/
|
||||||
|
.parcel-cache/
|
||||||
|
.angular/
|
||||||
|
|
||||||
|
# Coverage
|
||||||
|
coverage/
|
||||||
|
.nyc_output/
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
.dockerignore
|
||||||
|
|
||||||
|
# Database
|
||||||
|
*.db
|
||||||
|
*.sqlite
|
||||||
|
|
||||||
|
# Images (if large)
|
||||||
|
/backend/src/images/uploads/
|
||||||
|
|
||||||
|
# Temporary
|
||||||
|
tmp/
|
||||||
|
temp/
|
||||||
1078
DEPLOYMENT.md
Normal file
640
README.md
Normal file
@@ -0,0 +1,640 @@
|
|||||||
|
# Looking - PfosiLooking Monorepo
|
||||||
|
|
||||||
|
> **An art project exploring modern gay dating culture through interactive storytelling**
|
||||||
|
|
||||||
|
"Looking" is a documentary photography and art project by Nick Pfosi that examines the cultural landscape of gay dating apps like Grindr and modern LGBTQ+ meeting spaces. The project captures user stories, experiences, and narratives about contemporary gay dating culture through an interactive mobile application.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Table of Contents
|
||||||
|
|
||||||
|
- [Project Overview](#project-overview)
|
||||||
|
- [Tech Stack](#tech-stack)
|
||||||
|
- [Prerequisites](#prerequisites)
|
||||||
|
- [Quick Start](#quick-start)
|
||||||
|
- [Project Structure](#project-structure)
|
||||||
|
- [Available Scripts](#available-scripts)
|
||||||
|
- [Data Management](#data-management)
|
||||||
|
- [Documentation Links](#documentation-links)
|
||||||
|
- [Troubleshooting](#troubleshooting)
|
||||||
|
- [Technical Debt & Security](#technical-debt--security)
|
||||||
|
- [Future Modernization Path](#future-modernization-path)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎨 Project Overview
|
||||||
|
|
||||||
|
This monorepo contains the full-stack "Looking" application:
|
||||||
|
|
||||||
|
- **Frontend**: Ionic 3 / Angular 5 mobile application for browsing profiles and stories
|
||||||
|
- **Backend**: Express.js REST API with MongoDB for data management
|
||||||
|
- **DevContainer**: Complete Docker-based development environment
|
||||||
|
|
||||||
|
The name "Urnings" (seen in code) references a historical term for homosexual men, adding a scholarly dimension to the project's exploration of queer identity and dating culture.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠 Tech Stack
|
||||||
|
|
||||||
|
### Frontend ([app/](app/))
|
||||||
|
|
||||||
|
- **Framework**: Ionic 3.9.2 with Angular 5.0.3
|
||||||
|
- **Language**: TypeScript 2.4.2
|
||||||
|
- **Build Tool**: `@ionic/app-scripts` 3.2.4
|
||||||
|
- **UI Components**: Ionic Angular, Ionicons
|
||||||
|
- **HTTP**: Angular HTTP module with RxJS 5.5.2
|
||||||
|
- **Dev Server**: Port 8100
|
||||||
|
|
||||||
|
### Backend ([backend/](backend/))
|
||||||
|
|
||||||
|
- **Framework**: Express 4.14.0
|
||||||
|
- **Runtime**: Node.js 14.x
|
||||||
|
- **Database**: MongoDB 4.4
|
||||||
|
- **ODM**: Mongoose 4.7.4
|
||||||
|
- **Authentication**: JWT (jsonwebtoken 7.3.0)
|
||||||
|
- **Security**: PBKDF2 password hashing (233,335 iterations)
|
||||||
|
- **Image Processing**: Multer 1.2.0
|
||||||
|
- **Logging**: Winston 2.4.0, Morgan 1.7.0
|
||||||
|
- **Email**: Nodemailer 4.0.1
|
||||||
|
- **API Server**: Port 3069
|
||||||
|
|
||||||
|
### DevOps
|
||||||
|
|
||||||
|
- **DevContainer**: Node 14 (Debian Bullseye)
|
||||||
|
- **Database**: MongoDB 4.4
|
||||||
|
- **Database Admin**: Mongo Express (port 8081)
|
||||||
|
- **Production**: Docker Compose + Traefik + Let's Encrypt SSL
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ Prerequisites
|
||||||
|
|
||||||
|
Before you begin, ensure you have:
|
||||||
|
|
||||||
|
- **Docker Desktop** (latest version) - [Download here](https://www.docker.com/products/docker-desktop)
|
||||||
|
- **Visual Studio Code** (latest version) - [Download here](https://code.visualstudio.com/)
|
||||||
|
- **VS Code Remote - Containers extension** - [Install from marketplace](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
|
||||||
|
- **Git** (for cloning the repository)
|
||||||
|
|
||||||
|
> **Note**: You do NOT need Node.js, npm, or MongoDB installed locally. The DevContainer provides everything.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Quick Start
|
||||||
|
|
||||||
|
### Using DevContainer (Recommended)
|
||||||
|
|
||||||
|
1. **Clone the repository**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone <repository-url>
|
||||||
|
cd PfosiLooking-monorepo
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Open in VS Code**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
code .
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Reopen in Container**:
|
||||||
|
|
||||||
|
- VS Code should prompt: "Reopen in Container"
|
||||||
|
- Or manually: Press `F1` → "Dev Containers: Reopen in Container"
|
||||||
|
|
||||||
|
4. **Wait for setup** (first time only - 5-10 minutes):
|
||||||
|
|
||||||
|
- Container builds with Node 14 + Python 2.7
|
||||||
|
- Dependencies install automatically via `postCreateCommand.sh`
|
||||||
|
- MongoDB starts and initializes
|
||||||
|
|
||||||
|
5. **Start development servers**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev:all
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Access the application**:
|
||||||
|
- **Frontend**: http://localhost:8100
|
||||||
|
- **Backend API**: http://localhost:3069
|
||||||
|
- **MongoDB**: localhost:27017
|
||||||
|
- **Mongo Express**: http://localhost:8081 (admin/password)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📁 Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
PfosiLooking-monorepo/
|
||||||
|
├── app/ # Ionic 3 / Angular 5 frontend
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── app/ # App module and root component
|
||||||
|
│ │ ├── pages/ # Page components (grid, chat, profile, etc.)
|
||||||
|
│ │ ├── services/ # Data services (ProfileService)
|
||||||
|
│ │ └── assets/ # Static assets, images, data
|
||||||
|
│ └── package.json # Frontend dependencies
|
||||||
|
│
|
||||||
|
├── backend/ # Express.js API
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── routes/ # API route handlers
|
||||||
|
│ │ ├── models/ # Mongoose schemas
|
||||||
|
│ │ ├── modules/ # Utilities (auth, images, mailer, etc.)
|
||||||
|
│ │ └── bin/www # Server startup script
|
||||||
|
│ ├── data/ # Seed data (profiles.json)
|
||||||
|
│ └── package.json # Backend dependencies
|
||||||
|
│
|
||||||
|
├── .devcontainer/ # Development container configuration
|
||||||
|
│ ├── devcontainer.json # VS Code DevContainer config
|
||||||
|
│ ├── docker-compose.yml # Dev services (app, mongo, mongo-express)
|
||||||
|
│ ├── Dockerfile # Custom dev image (Node 14 + Python 2.7)
|
||||||
|
│ └── postCreateCommand.sh # Auto-install dependencies
|
||||||
|
│
|
||||||
|
├── docker-compose.yml # Production deployment configuration
|
||||||
|
├── package.json # Root workspace configuration
|
||||||
|
└── README.md # This file
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📜 Available Scripts
|
||||||
|
|
||||||
|
### Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start both frontend and backend concurrently
|
||||||
|
npm run dev:all
|
||||||
|
|
||||||
|
# Start only backend (port 3069)
|
||||||
|
npm run dev:backend
|
||||||
|
|
||||||
|
# Start only frontend (port 8100)
|
||||||
|
npm run dev:app
|
||||||
|
```
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install all dependencies (root + workspaces)
|
||||||
|
npm run install:all
|
||||||
|
|
||||||
|
# Install workspace dependencies only
|
||||||
|
npm run install:workspaces
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Seed MongoDB with initial data from backend/data/profiles.json
|
||||||
|
npm run seed
|
||||||
|
|
||||||
|
# Access MongoDB shell
|
||||||
|
npm run mongo:shell
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build & Deploy
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build both frontend and backend
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# Build Docker images for production
|
||||||
|
npm run docker:build
|
||||||
|
|
||||||
|
# Deploy to production (docker-compose up)
|
||||||
|
npm run deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing & Quality
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run all tests (backend + frontend)
|
||||||
|
npm test
|
||||||
|
|
||||||
|
# Run backend tests only (Mocha/Chai)
|
||||||
|
npm run test:backend
|
||||||
|
|
||||||
|
# Run frontend tests only
|
||||||
|
npm run test:app
|
||||||
|
|
||||||
|
# Lint all code
|
||||||
|
npm run lint
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💾 Data Management
|
||||||
|
|
||||||
|
### ⚠️ Important: No Interactive Data Entry
|
||||||
|
|
||||||
|
**This application currently does NOT support adding or editing data through the UI.** All profile and message data is seeded from static JSON files.
|
||||||
|
|
||||||
|
### How to Add/Edit Content
|
||||||
|
|
||||||
|
1. **Edit the seed data file**: [`backend/data/profiles.json`](backend/data/profiles.json)
|
||||||
|
|
||||||
|
2. **Re-seed the database**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run seed
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **For production deployments**: The database is wiped and reseeded on each deployment, so all changes must be committed to `profiles.json` before deploying.
|
||||||
|
|
||||||
|
### Data Structure
|
||||||
|
|
||||||
|
Profiles are stored in `backend/data/profiles.json` with this structure:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"order": 1,
|
||||||
|
"details": {
|
||||||
|
"name": "John",
|
||||||
|
"age": 28,
|
||||||
|
"location": "San Francisco, CA",
|
||||||
|
"about": "User's story...",
|
||||||
|
"pic": {
|
||||||
|
"thumb": "profile/john_thumbnail.png",
|
||||||
|
"detail": "profile/john_detail.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"text": "What's your story?",
|
||||||
|
"isUser": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "I've been using dating apps for...",
|
||||||
|
"isUser": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"submitted": true,
|
||||||
|
"approved": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 Documentation Links
|
||||||
|
|
||||||
|
- **[App Documentation](app/README.md)** - Frontend architecture, pages, and services
|
||||||
|
- **[Backend Documentation](backend/README.md)** - API server, routes, and models
|
||||||
|
- **[API Reference](backend/API.md)** - Complete REST API endpoint documentation
|
||||||
|
- **[Database Schema](backend/SCHEMA.md)** - MongoDB collection schemas and relationships
|
||||||
|
- **[DevContainer Guide](.devcontainer/README.md)** - Development environment setup
|
||||||
|
- **[Deployment Guide](DEPLOYMENT.md)** - Production deployment with Docker and Traefik
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Troubleshooting
|
||||||
|
|
||||||
|
### DevContainer Issues
|
||||||
|
|
||||||
|
**Problem**: Container fails to start or build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Solution: Rebuild container without cache
|
||||||
|
F1 → "Dev Containers: Rebuild Container Without Cache"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problem**: Port conflicts (8100, 3069, 27017, 8081 already in use)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Solution: Stop conflicting services
|
||||||
|
docker ps # Find conflicting containers
|
||||||
|
docker stop <container-id>
|
||||||
|
|
||||||
|
# Or change ports in .devcontainer/docker-compose.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problem**: `node_modules` issues or permission errors
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Solution: Remove node_modules and reinstall
|
||||||
|
rm -rf app/node_modules backend/node_modules node_modules
|
||||||
|
npm run install:all
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontend Issues
|
||||||
|
|
||||||
|
**Problem**: `node-sass` build failed
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Solution: Python 2.7 is required (included in DevContainer)
|
||||||
|
# If running locally without DevContainer:
|
||||||
|
npm install --legacy-peer-deps
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problem**: Peer dependency warnings during `npm install`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Solution: Use legacy peer deps flag (included in postCreateCommand.sh)
|
||||||
|
cd app
|
||||||
|
npm install --legacy-peer-deps
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problem**: Module not found errors
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Solution: Clear cache and reinstall
|
||||||
|
cd app
|
||||||
|
rm -rf node_modules package-lock.json
|
||||||
|
npm install --legacy-peer-deps
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problem**: JavaScript heap out of memory
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Solution: Increase Node.js memory limit
|
||||||
|
export NODE_OPTIONS="--max-old-space-size=4096"
|
||||||
|
npm run dev:app
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backend Issues
|
||||||
|
|
||||||
|
**Problem**: MongoDB connection failed
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Solution: Verify MongoDB container is running
|
||||||
|
docker ps | grep mongo
|
||||||
|
|
||||||
|
# Check MongoDB health
|
||||||
|
docker exec -it <mongo-container-id> mongo --eval "db.adminCommand('ping')"
|
||||||
|
|
||||||
|
# Restart MongoDB service
|
||||||
|
docker-compose -f .devcontainer/docker-compose.yml restart mongo
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problem**: JWT token invalid errors
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Solution: Verify JWT_SECRET is set in .devcontainer/dev.env
|
||||||
|
# Check backend/.env.example for required environment variables
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problem**: Image upload fails
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Solution: Check volume permissions and multer configuration
|
||||||
|
# Verify backend/src/images directory exists and is writable
|
||||||
|
ls -la backend/src/images
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problem**: Cannot connect to API from frontend
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Solution: Verify backend is running on port 3069
|
||||||
|
curl http://localhost:3069/profiles
|
||||||
|
|
||||||
|
# Check CORS settings in backend/src/app.js
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Issues
|
||||||
|
|
||||||
|
**Problem**: Seed data not loading
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Solution: Manually run seed script
|
||||||
|
cd backend
|
||||||
|
npm run seed
|
||||||
|
|
||||||
|
# Or check data file exists
|
||||||
|
ls -la backend/data/profiles.json
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problem**: Mongo Express not accessible
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Solution: Verify service is running and credentials are correct
|
||||||
|
# Default: http://localhost:8081 (admin/password)
|
||||||
|
docker logs <mongo-express-container-id>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ Technical Debt & Security
|
||||||
|
|
||||||
|
### Outdated Dependencies
|
||||||
|
|
||||||
|
This project uses **legacy technologies** (circa 2017-2018) that are significantly outdated:
|
||||||
|
|
||||||
|
| Component | Current Version | Latest Stable | Status |
|
||||||
|
| ------------ | --------------- | ------------- | --------------------------- |
|
||||||
|
| **Node.js** | 14.x | 20.x LTS | ⛔ EOL April 2023 |
|
||||||
|
| **Angular** | 5.0.3 | 17.x | ⛔ 12 major versions behind |
|
||||||
|
| **Ionic** | 3.9.2 | 7.x | ⛔ 4 major versions behind |
|
||||||
|
| **Express** | 4.14.0 | 4.18.x | ⚠️ Missing security patches |
|
||||||
|
| **MongoDB** | 4.4 | 7.0 | ⚠️ 3 major versions behind |
|
||||||
|
| **Mongoose** | 4.7.4 | 8.x | ⛔ 4 major versions behind |
|
||||||
|
|
||||||
|
### Security Concerns
|
||||||
|
|
||||||
|
1. **CORS Configuration**: Wide open (`Access-Control-Allow-Origin: *`)
|
||||||
|
|
||||||
|
- **Risk**: Any domain can make requests to API
|
||||||
|
- **Fix**: Restrict to specific origins in production (already configured in docker-compose.yml)
|
||||||
|
|
||||||
|
2. **No Rate Limiting**: API has no request throttling
|
||||||
|
|
||||||
|
- **Risk**: Vulnerable to brute force and DoS attacks
|
||||||
|
- **Fix**: Add `express-rate-limit` middleware
|
||||||
|
|
||||||
|
3. **Missing Input Validation**: No validation library
|
||||||
|
|
||||||
|
- **Risk**: Potential injection attacks
|
||||||
|
- **Fix**: Add Joi or express-validator
|
||||||
|
|
||||||
|
4. **JWT Secret Management**: Secret must be set via environment variable
|
||||||
|
|
||||||
|
- **Risk**: If leaked, tokens can be forged
|
||||||
|
- **Fix**: Use strong secrets, rotate regularly, consider refresh tokens
|
||||||
|
|
||||||
|
5. **Image Upload Security**: No file type/size restrictions visible
|
||||||
|
|
||||||
|
- **Risk**: Malicious file uploads
|
||||||
|
- **Fix**: Validate MIME types, set size limits, sanitize filenames
|
||||||
|
|
||||||
|
6. **Node.js 14 EOL**: No longer receives security updates
|
||||||
|
- **Risk**: Unpatched vulnerabilities
|
||||||
|
- **Fix**: Upgrade to Node 20 LTS (see modernization path below)
|
||||||
|
|
||||||
|
### Code Quality Issues
|
||||||
|
|
||||||
|
- No TypeScript in backend (JavaScript only)
|
||||||
|
- Limited test coverage (backend has Mocha tests, frontend has none)
|
||||||
|
- No automated CI/CD pipeline
|
||||||
|
- `ionic-app-scripts` is deprecated
|
||||||
|
- Gulp is dated for task running
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Future Modernization Path
|
||||||
|
|
||||||
|
For developers tasked with modernizing this codebase, follow this **incremental upgrade strategy**:
|
||||||
|
|
||||||
|
### Phase 1: Critical Security Updates (2-4 weeks)
|
||||||
|
|
||||||
|
1. **Upgrade Node.js**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Update Dockerfile to use Node 20 LTS
|
||||||
|
FROM node:20-bullseye
|
||||||
|
|
||||||
|
# Remove Python 2.7 symlinks (modern node-sass doesn't need it)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Update Backend Dependencies**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd backend
|
||||||
|
npm update express mongoose jsonwebtoken
|
||||||
|
npm install express-rate-limit express-validator helmet
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Add Security Middleware**:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// backend/src/app.js
|
||||||
|
const rateLimit = require("express-rate-limit");
|
||||||
|
const helmet = require("helmet");
|
||||||
|
const { body, validationResult } = require("express-validator");
|
||||||
|
|
||||||
|
app.use(helmet());
|
||||||
|
app.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }));
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Fix CORS** (already done in production docker-compose.yml):
|
||||||
|
```javascript
|
||||||
|
// Restrict to specific origin
|
||||||
|
res.header("Access-Control-Allow-Origin", "https://pfosi.mifi.dev");
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 2: Backend Modernization (4-6 weeks)
|
||||||
|
|
||||||
|
1. **Convert Backend to TypeScript**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -D typescript @types/express @types/node @types/mongoose
|
||||||
|
npx tsc --init
|
||||||
|
# Incrementally convert .js files to .ts
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Upgrade MongoDB & Mongoose**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Update docker-compose.yml
|
||||||
|
mongo:
|
||||||
|
image: mongo:7.0
|
||||||
|
|
||||||
|
# Update Mongoose
|
||||||
|
npm install mongoose@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Modern Testing Setup**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -D jest ts-jest @types/jest supertest
|
||||||
|
# Migrate from Mocha/Chai to Jest
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Add API Documentation**:
|
||||||
|
```bash
|
||||||
|
npm install swagger-jsdoc swagger-ui-express
|
||||||
|
# Generate OpenAPI/Swagger docs from code
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 3: Frontend Modernization (8-12 weeks)
|
||||||
|
|
||||||
|
This is the **most complex** phase due to breaking changes.
|
||||||
|
|
||||||
|
**Option A: Incremental Angular Upgrade** (Slower but safer)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Upgrade through each major version
|
||||||
|
ng update @angular/cli@6 @angular/core@6
|
||||||
|
ng update @angular/cli@7 @angular/core@7
|
||||||
|
# ... continue through each version to 17
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option B: Fresh Ionic 7 Rewrite** (Faster, cleaner)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create new Ionic 7 app with Angular 17
|
||||||
|
ionic start looking-v2 tabs --type=angular
|
||||||
|
# Port components one by one
|
||||||
|
# Benefits: Standalone components, Signals, better performance
|
||||||
|
```
|
||||||
|
|
||||||
|
**Recommendation**: For novice developers, **Option B (rewrite)** is recommended because:
|
||||||
|
|
||||||
|
- Angular 5 → 17 migration is complex with many breaking changes
|
||||||
|
- Ionic 3 → 7 requires complete UI component rewrites
|
||||||
|
- Fresh start allows modern architecture (standalone components, no NgModules)
|
||||||
|
- Cleaner codebase without legacy workarounds
|
||||||
|
|
||||||
|
### Phase 4: Modern Build Pipeline (2-3 weeks)
|
||||||
|
|
||||||
|
1. **Replace deprecated build tools**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Remove ionic-app-scripts, use Angular CLI
|
||||||
|
# Remove Gulp, use npm scripts
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Add CI/CD**:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# .github/workflows/ci.yml or .gitlab-ci.yml
|
||||||
|
- Run tests on every commit
|
||||||
|
- Automated Docker builds
|
||||||
|
- Deployment to staging/production
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Code Quality Tools**:
|
||||||
|
```bash
|
||||||
|
npm install -D eslint prettier husky lint-staged
|
||||||
|
# Pre-commit hooks for linting
|
||||||
|
```
|
||||||
|
|
||||||
|
### Estimated Total Effort
|
||||||
|
|
||||||
|
- **Phase 1 (Security)**: 2-4 weeks - **START HERE**
|
||||||
|
- **Phase 2 (Backend)**: 4-6 weeks
|
||||||
|
- **Phase 3 (Frontend)**: 8-12 weeks - **Most complex**
|
||||||
|
- **Phase 4 (DevOps)**: 2-3 weeks
|
||||||
|
|
||||||
|
**Total**: 16-25 weeks (4-6 months) for full modernization
|
||||||
|
|
||||||
|
### Resources for Learning
|
||||||
|
|
||||||
|
- [Angular Update Guide](https://update.angular.io/) - Step-by-step migration instructions
|
||||||
|
- [Ionic Migration Guide](https://ionicframework.com/docs/intro/upgrading-to-ionic-7)
|
||||||
|
- [Node.js Best Practices](https://github.com/goldbergyoni/nodebestpractices)
|
||||||
|
- [Express Security Best Practices](https://expressjs.com/en/advanced/best-practice-security.html)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🤝 Contributing
|
||||||
|
|
||||||
|
This is an art project with specific artistic intent. Before making changes:
|
||||||
|
|
||||||
|
1. Understand the artistic and documentary purpose
|
||||||
|
2. Preserve the original narrative and user stories
|
||||||
|
3. Consult with the artist (Nick Pfosi) for content changes
|
||||||
|
4. Follow the modernization path for technical improvements
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
See project repository for license information.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 👤 Author
|
||||||
|
|
||||||
|
**Nick Pfosi** - Artist & Creator
|
||||||
|
**Mike Fitzpatrick** - Technical Implementation (badmf@mifi.dev)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Questions or Issues?** Check the [troubleshooting section](#troubleshooting) or refer to component-specific documentation linked above.
|
||||||
17
app/.editorconfig
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# EditorConfig helps developers define and maintain consistent coding styles between different editors and IDEs
|
||||||
|
# editorconfig.org
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
# We recommend you to keep these unchanged
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
35
app/.gitignore
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Specifies intentionally untracked files to ignore when using Git
|
||||||
|
# http://git-scm.com/docs/gitignore
|
||||||
|
|
||||||
|
*~
|
||||||
|
*.sw[mnpcod]
|
||||||
|
*.log
|
||||||
|
*.tmp
|
||||||
|
*.tmp.*
|
||||||
|
log.txt
|
||||||
|
*.sublime-project
|
||||||
|
*.sublime-workspace
|
||||||
|
.vscode/
|
||||||
|
npm-debug.log*
|
||||||
|
|
||||||
|
.idea/
|
||||||
|
.sourcemaps/
|
||||||
|
.sass-cache/
|
||||||
|
.tmp/
|
||||||
|
.versions/
|
||||||
|
coverage/
|
||||||
|
dist/
|
||||||
|
node_modules/
|
||||||
|
tmp/
|
||||||
|
temp/
|
||||||
|
hooks/
|
||||||
|
platforms/
|
||||||
|
plugins/
|
||||||
|
plugins/android.json
|
||||||
|
plugins/ios.json
|
||||||
|
www/
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
UserInterfaceState.xcuserstate
|
||||||
713
app/README.md
Normal file
@@ -0,0 +1,713 @@
|
|||||||
|
# Looking - Frontend Application
|
||||||
|
|
||||||
|
> **Ionic 3 / Angular 5 mobile application for browsing dating app profiles and stories**
|
||||||
|
|
||||||
|
This is the frontend mobile application for the "Looking" art project. Built with Ionic 3 and Angular 5, it provides an interactive interface for exploring user profiles, stories, and messages about modern gay dating culture.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Table of Contents
|
||||||
|
|
||||||
|
- [Overview](#overview)
|
||||||
|
- [Tech Stack](#tech-stack)
|
||||||
|
- [Project Structure](#project-structure)
|
||||||
|
- [Pages & Components](#pages--components)
|
||||||
|
- [Services](#services)
|
||||||
|
- [Data Flow](#data-flow)
|
||||||
|
- [Development](#development)
|
||||||
|
- [Building](#building)
|
||||||
|
- [Troubleshooting](#troubleshooting)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Overview
|
||||||
|
|
||||||
|
The frontend is a **hybrid mobile application** that can run as:
|
||||||
|
|
||||||
|
- Web application (development mode on port 8100)
|
||||||
|
- iOS native app (requires Xcode)
|
||||||
|
- Android native app (requires Android Studio)
|
||||||
|
|
||||||
|
**Key Features:**
|
||||||
|
|
||||||
|
- Grid-based profile browsing (Tinder-like swipe interface)
|
||||||
|
- Detailed profile views with images and user stories
|
||||||
|
- Message threads with conversation history
|
||||||
|
- Story submission form ("Tell Your Story")
|
||||||
|
- Tab-based navigation
|
||||||
|
- Offline support with local JSON fallback
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠 Tech Stack
|
||||||
|
|
||||||
|
| Technology | Version | Purpose |
|
||||||
|
| ------------------- | ------- | ---------------------------------------- |
|
||||||
|
| **Ionic Framework** | 3.9.2 | Hybrid mobile framework |
|
||||||
|
| **Angular** | 5.0.3 | Component framework |
|
||||||
|
| **TypeScript** | 2.4.2 | Type-safe JavaScript |
|
||||||
|
| **RxJS** | 5.5.2 | Reactive programming |
|
||||||
|
| **@angular/http** | 5.0.3 | HTTP client (deprecated, pre-HttpClient) |
|
||||||
|
| **ionic-swipe-all** | 1.2.0 | Swipe gesture library |
|
||||||
|
| **moment** | 2.21.0 | Date/time formatting |
|
||||||
|
| **@ionic/storage** | 2.1.3 | Local storage abstraction |
|
||||||
|
| **ionicons** | 3.0.0 | Icon library |
|
||||||
|
|
||||||
|
**Build Tool**: `@ionic/app-scripts` 3.2.4 (Ionic's custom webpack wrapper)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📁 Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
app/
|
||||||
|
├── src/
|
||||||
|
│ ├── app/ # Root application module
|
||||||
|
│ │ ├── app.component.ts # Root component (title: "Urnings")
|
||||||
|
│ │ ├── app.module.ts # Main NgModule with declarations
|
||||||
|
│ │ ├── app.html # Root template
|
||||||
|
│ │ ├── app.scss # Global styles
|
||||||
|
│ │ └── main.ts # Bootstrap entry point
|
||||||
|
│ │
|
||||||
|
│ ├── pages/ # Page components (routable views)
|
||||||
|
│ │ ├── tabs/ # Tab navigation container
|
||||||
|
│ │ │ ├── tabs.ts # TabsPage component
|
||||||
|
│ │ │ └── tabs.html # Tab layout (3 tabs)
|
||||||
|
│ │ │
|
||||||
|
│ │ ├── grid/ # Profile grid view (main browse page)
|
||||||
|
│ │ │ ├── grid.ts # Grid logic with swipe handling
|
||||||
|
│ │ │ ├── grid.html # Grid layout with profile cards
|
||||||
|
│ │ │ └── grid.scss # Grid styles
|
||||||
|
│ │ │
|
||||||
|
│ │ ├── profile/ # Detailed profile view
|
||||||
|
│ │ │ ├── profile.ts # Profile display logic
|
||||||
|
│ │ │ ├── profile.html # Profile template (name, age, about, pics)
|
||||||
|
│ │ │ └── profile.scss # Profile styles
|
||||||
|
│ │ │
|
||||||
|
│ │ ├── chat/ # Individual conversation view
|
||||||
|
│ │ │ ├── chat.ts # Chat message display
|
||||||
|
│ │ │ ├── chat.html # Message thread UI
|
||||||
|
│ │ │ └── chat.scss # Chat bubble styles
|
||||||
|
│ │ │
|
||||||
|
│ │ ├── messages/ # Message list/inbox view
|
||||||
|
│ │ │ ├── messages.ts # Message list logic
|
||||||
|
│ │ │ ├── messages.html # List of conversations
|
||||||
|
│ │ │ └── messages.scss # Message list styles
|
||||||
|
│ │ │
|
||||||
|
│ │ ├── users/ # User list view
|
||||||
|
│ │ │ ├── users.ts # User list logic
|
||||||
|
│ │ │ ├── users.html # User grid/list
|
||||||
|
│ │ │ └── users.scss # User list styles
|
||||||
|
│ │ │
|
||||||
|
│ │ ├── lightbox/ # Full-screen image viewer
|
||||||
|
│ │ │ ├── lightbox.ts # Image zoom/pan logic
|
||||||
|
│ │ │ ├── lightbox.html # Fullscreen image display
|
||||||
|
│ │ │ └── lightbox.scss # Lightbox styles
|
||||||
|
│ │ │
|
||||||
|
│ │ ├── information/ # Info/about page
|
||||||
|
│ │ │ ├── information.ts # Static info page
|
||||||
|
│ │ │ ├── information.html # Project information
|
||||||
|
│ │ │ └── information.scss # Info page styles
|
||||||
|
│ │ │
|
||||||
|
│ │ └── tell/ # Story submission form
|
||||||
|
│ │ ├── tell.ts # Form submission logic
|
||||||
|
│ │ ├── tell.html # Story submission form
|
||||||
|
│ │ └── tell.scss # Form styles
|
||||||
|
│ │
|
||||||
|
│ ├── services/ # Shared services
|
||||||
|
│ │ └── profiles.ts # ProfileService for data management
|
||||||
|
│ │
|
||||||
|
│ ├── theme/ # Global theme configuration
|
||||||
|
│ │ └── variables.scss # Ionic theme variables
|
||||||
|
│ │
|
||||||
|
│ ├── assets/ # Static assets
|
||||||
|
│ │ ├── data/ # Local JSON data files (fallback)
|
||||||
|
│ │ │ ├── profiles.json # All profiles
|
||||||
|
│ │ │ ├── profiles initial.json # Initial seed data
|
||||||
|
│ │ │ └── cruises.json # Additional data
|
||||||
|
│ │ ├── icon/ # App icons
|
||||||
|
│ │ ├── images/ # Profile, message images
|
||||||
|
│ │ │ ├── profile/ # Profile photos
|
||||||
|
│ │ │ ├── message/ # Message attachments
|
||||||
|
│ │ │ └── cruise/ # Cruise-related images
|
||||||
|
│ │ └── imgs/ # UI images and icons
|
||||||
|
│ │
|
||||||
|
│ ├── index.html # Main HTML entry
|
||||||
|
│ ├── manifest.json # PWA manifest
|
||||||
|
│ └── service-worker.js # Service worker for offline support
|
||||||
|
│
|
||||||
|
├── www/ # Build output directory
|
||||||
|
│ ├── build/ # Compiled JS/CSS bundles
|
||||||
|
│ │ ├── main.js # Application code
|
||||||
|
│ │ ├── vendor.js # Third-party libraries
|
||||||
|
│ │ ├── polyfills.js # Browser polyfills
|
||||||
|
│ │ └── main.css # Compiled styles
|
||||||
|
│ └── assets/ # Copied static assets
|
||||||
|
│
|
||||||
|
├── package.json # Dependencies and scripts
|
||||||
|
├── ionic.config.json # Ionic CLI configuration
|
||||||
|
├── tsconfig.json # TypeScript compiler config
|
||||||
|
├── tslint.json # TSLint rules (deprecated)
|
||||||
|
└── .editorconfig # Editor formatting rules
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📄 Pages & Components
|
||||||
|
|
||||||
|
### TabsPage (`pages/tabs/`)
|
||||||
|
|
||||||
|
**Root navigation container** with three tabs:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// tabs.ts - Tab configuration
|
||||||
|
tabs = [
|
||||||
|
{ root: GridPage, tabTitle: "Grid", tabIcon: "grid" },
|
||||||
|
{ root: UsersPage, tabTitle: "Users", tabIcon: "people" },
|
||||||
|
{ root: MessagesPage, tabTitle: "Messages", tabIcon: "chatboxes" },
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
### GridPage (`pages/grid/`)
|
||||||
|
|
||||||
|
**Main profile browsing interface** - Tinder-like grid:
|
||||||
|
|
||||||
|
- Displays profile thumbnails in grid layout
|
||||||
|
- Supports swipe gestures (using `ionic-swipe-all`)
|
||||||
|
- Taps open detailed profile view
|
||||||
|
- Loads profiles from ProfileService
|
||||||
|
- Falls back to local JSON if API unavailable
|
||||||
|
|
||||||
|
**Key Features:**
|
||||||
|
|
||||||
|
- Swipe left/right for navigation
|
||||||
|
- Pull-to-refresh
|
||||||
|
- Infinite scroll
|
||||||
|
- Click thumbnail → ProfilePage
|
||||||
|
|
||||||
|
### ProfilePage (`pages/profile/`)
|
||||||
|
|
||||||
|
**Detailed profile view** with:
|
||||||
|
|
||||||
|
- Profile photo (large detail image)
|
||||||
|
- Name, age, location
|
||||||
|
- "About" section with user story
|
||||||
|
- Multiple photos in gallery
|
||||||
|
- Message thread (if profile has messages)
|
||||||
|
- Navigation to previous/next profile
|
||||||
|
|
||||||
|
**Navigation:**
|
||||||
|
|
||||||
|
- Receives profile ID via NavParams
|
||||||
|
- "Next" and "Previous" buttons
|
||||||
|
- Photo gallery opens LightboxPage
|
||||||
|
|
||||||
|
### ChatPage (`pages/chat/`)
|
||||||
|
|
||||||
|
**Message thread view**:
|
||||||
|
|
||||||
|
- Displays conversation history
|
||||||
|
- Question/answer format
|
||||||
|
- User responses vs. interviewer questions
|
||||||
|
- Image attachments supported
|
||||||
|
- Timestamps with moment.js
|
||||||
|
|
||||||
|
**Message Types:**
|
||||||
|
|
||||||
|
- `isUser: false` → Questions (left-aligned)
|
||||||
|
- `isUser: true` → User responses (right-aligned)
|
||||||
|
|
||||||
|
### MessagesPage (`pages/messages/`)
|
||||||
|
|
||||||
|
**Inbox/conversation list**:
|
||||||
|
|
||||||
|
- Shows all profiles with messages
|
||||||
|
- Displays message preview
|
||||||
|
- Unread indicators
|
||||||
|
- Tap to open ChatPage with full thread
|
||||||
|
|
||||||
|
### UsersPage (`pages/users/`)
|
||||||
|
|
||||||
|
**User list view**:
|
||||||
|
|
||||||
|
- Alternative view to grid
|
||||||
|
- List format with thumbnails
|
||||||
|
- Quick access to profiles
|
||||||
|
- Filter/search options (if implemented)
|
||||||
|
|
||||||
|
### LightboxPage (`pages/lightbox/`)
|
||||||
|
|
||||||
|
**Full-screen image viewer**:
|
||||||
|
|
||||||
|
- Zoom and pan gestures
|
||||||
|
- Swipe between photos
|
||||||
|
- Close button returns to profile
|
||||||
|
- Supports pinch-to-zoom
|
||||||
|
|
||||||
|
### InformationPage (`pages/information/`)
|
||||||
|
|
||||||
|
**Static information page**:
|
||||||
|
|
||||||
|
- About the art project
|
||||||
|
- Artist statement
|
||||||
|
- Project goals
|
||||||
|
- Credits and acknowledgments
|
||||||
|
|
||||||
|
### TellPage (`pages/tell/`)
|
||||||
|
|
||||||
|
**Story submission form**:
|
||||||
|
|
||||||
|
- Users can submit their own stories
|
||||||
|
- Form fields: name, age, location, story
|
||||||
|
- Photo upload
|
||||||
|
- Submission sends to API (if connected)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔌 Services
|
||||||
|
|
||||||
|
### ProfileService (`services/profiles.ts`)
|
||||||
|
|
||||||
|
**Central data management service** for profiles.
|
||||||
|
|
||||||
|
**Endpoints:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
endpoint: "http://localhost:27017/urnings/profiles"; // Primary API
|
||||||
|
fallback: "assets/data/profiles.json"; // Local fallback
|
||||||
|
epSubmitted: "/submitted"; // Submitted profiles
|
||||||
|
epVerified: "/verified"; // Verified profiles
|
||||||
|
```
|
||||||
|
|
||||||
|
**Methods:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
load(); // Load all profiles
|
||||||
|
loadSubmitted(); // Load submitted (pending) profiles
|
||||||
|
loadVerified(); // Load verified (approved) profiles
|
||||||
|
getProfiles(); // Get cached profiles
|
||||||
|
getProfileById(id); // Get specific profile
|
||||||
|
getNextProfile(id); // Navigate to next profile
|
||||||
|
getPreviousProfile(id); // Navigate to previous
|
||||||
|
```
|
||||||
|
|
||||||
|
**Data Flow:**
|
||||||
|
|
||||||
|
1. Tries to fetch from API endpoint
|
||||||
|
2. On error, falls back to local `assets/data/profiles.json`
|
||||||
|
3. Caches data in memory
|
||||||
|
4. Builds ID→index map for navigation
|
||||||
|
|
||||||
|
**Fallback Behavior:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
doGetRequest(endpoint, resolve, type) {
|
||||||
|
this.http.get(endpoint).subscribe(
|
||||||
|
(data) => { /* Use API data */ },
|
||||||
|
(error) => {
|
||||||
|
// API failed → use local JSON
|
||||||
|
this.doGetRequest(this.fallback, resolve, type);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 Data Flow
|
||||||
|
|
||||||
|
### Profile Loading Flow
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[GridPage loads] --> B{ProfileService.load}
|
||||||
|
B --> C[Try API: http://localhost:27017/urnings/profiles]
|
||||||
|
C --> D{API Success?}
|
||||||
|
D -->|Yes| E[Store profiles in memory]
|
||||||
|
D -->|No| F[Fallback to assets/data/profiles.json]
|
||||||
|
F --> E
|
||||||
|
E --> G[Build ID-to-index map]
|
||||||
|
G --> H[Return profiles to GridPage]
|
||||||
|
H --> I[Display grid of thumbnails]
|
||||||
|
I --> J[User taps profile]
|
||||||
|
J --> K[Navigate to ProfilePage with ID]
|
||||||
|
K --> L[ProfilePage fetches profile from service]
|
||||||
|
L --> M[Display full profile details]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Message Loading Flow
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[ProfilePage loads] --> B[Get profile by ID]
|
||||||
|
B --> C{Profile has messages?}
|
||||||
|
C -->|Yes| D[Extract messages array]
|
||||||
|
C -->|No| E[Show 'No messages']
|
||||||
|
D --> F[Display in ChatPage]
|
||||||
|
F --> G[Format with moment.js]
|
||||||
|
G --> H[Render question/answer pairs]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💻 Development
|
||||||
|
|
||||||
|
### Start Dev Server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From project root
|
||||||
|
npm run dev:app
|
||||||
|
|
||||||
|
# Or from app directory
|
||||||
|
cd app
|
||||||
|
ionic serve --host=0.0.0.0 --port=8100
|
||||||
|
```
|
||||||
|
|
||||||
|
**Access**: http://localhost:8100
|
||||||
|
|
||||||
|
### Live Reload
|
||||||
|
|
||||||
|
Ionic's dev server watches for file changes and automatically:
|
||||||
|
|
||||||
|
- Recompiles TypeScript
|
||||||
|
- Rebuilds SCSS
|
||||||
|
- Reloads browser
|
||||||
|
- Preserves dev tools open
|
||||||
|
|
||||||
|
### Chrome DevTools
|
||||||
|
|
||||||
|
Open Chrome DevTools to:
|
||||||
|
|
||||||
|
- Inspect components
|
||||||
|
- Debug TypeScript (source maps enabled)
|
||||||
|
- Test responsive layouts
|
||||||
|
- Simulate mobile devices
|
||||||
|
- Monitor network requests
|
||||||
|
|
||||||
|
### API Configuration
|
||||||
|
|
||||||
|
To switch between local and remote API:
|
||||||
|
|
||||||
|
**Edit `services/profiles.ts`:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Local API (backend running on port 3069)
|
||||||
|
endpoint: "http://localhost:3069/profiles";
|
||||||
|
|
||||||
|
// Remote API (production)
|
||||||
|
endpoint: "https://api.pfosi.mifi.dev/profiles";
|
||||||
|
|
||||||
|
// Fallback (always local)
|
||||||
|
fallback: "assets/data/profiles.json";
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🏗️ Building
|
||||||
|
|
||||||
|
### Development Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
# or
|
||||||
|
ionic build
|
||||||
|
```
|
||||||
|
|
||||||
|
Output: `www/` directory
|
||||||
|
|
||||||
|
### Production Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ionic build --prod
|
||||||
|
```
|
||||||
|
|
||||||
|
**Optimizations:**
|
||||||
|
|
||||||
|
- Minification
|
||||||
|
- Tree shaking
|
||||||
|
- Ahead-of-time (AOT) compilation
|
||||||
|
- CSS purging
|
||||||
|
- Source map generation (optional)
|
||||||
|
|
||||||
|
### Build for Mobile Platforms
|
||||||
|
|
||||||
|
**iOS**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ionic cordova build ios --prod --release
|
||||||
|
# Requires Xcode and iOS development certificates
|
||||||
|
```
|
||||||
|
|
||||||
|
**Android**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ionic cordova build android --prod --release
|
||||||
|
# Requires Android Studio and signing keys
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**: Mobile builds require additional Cordova setup not covered here.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Troubleshooting
|
||||||
|
|
||||||
|
### `node-sass` Build Failed
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
|
||||||
|
```
|
||||||
|
Error: `gyp` failed with exit code: 1
|
||||||
|
Module 'node-sass' not found
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# node-sass requires Python 2.7 (included in DevContainer)
|
||||||
|
# If running locally:
|
||||||
|
npm rebuild node-sass --force
|
||||||
|
|
||||||
|
# Or use legacy peer deps
|
||||||
|
npm install --legacy-peer-deps
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why?** `node-sass` is a native module that requires Python 2.7 and build tools. The DevContainer includes these dependencies via symlinks.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Peer Dependency Warnings
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
|
||||||
|
```
|
||||||
|
npm WARN ERESOLVE overriding peer dependency
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Use legacy peer dependency resolution (npm 7+)
|
||||||
|
npm install --legacy-peer-deps
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why?** Ionic 3 and Angular 5 were built before npm 7's strict peer dependency checking. The `--legacy-peer-deps` flag uses npm 6 behavior.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Module Not Found Errors
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
|
||||||
|
```
|
||||||
|
Error: Cannot find module '@angular/core'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Remove node_modules and reinstall
|
||||||
|
rm -rf node_modules package-lock.json
|
||||||
|
npm install --legacy-peer-deps
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### JavaScript Heap Out of Memory
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
|
||||||
|
```
|
||||||
|
FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Increase Node.js memory limit
|
||||||
|
export NODE_OPTIONS="--max-old-space-size=4096"
|
||||||
|
npm run ionic:serve
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Ionic Serve Port Already in Use
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
|
||||||
|
```
|
||||||
|
Error: Port 8100 is already in use
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Find process using port 8100
|
||||||
|
lsof -i :8100
|
||||||
|
|
||||||
|
# Kill the process
|
||||||
|
kill -9 <PID>
|
||||||
|
|
||||||
|
# Or use different port
|
||||||
|
ionic serve --port=8101
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### TypeScript Compilation Errors
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
|
||||||
|
```
|
||||||
|
Error: TS2304: Cannot find name 'Promise'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Verify TypeScript version matches project
|
||||||
|
npm install typescript@2.4.2 --save-dev --save-exact
|
||||||
|
```
|
||||||
|
|
||||||
|
**Check `tsconfig.json`:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["es2015", "dom"],
|
||||||
|
"target": "es5"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### CORS Errors in Browser
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
|
||||||
|
```
|
||||||
|
Access to XMLHttpRequest at 'http://localhost:3069/profiles' blocked by CORS policy
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
|
||||||
|
**Option 1**: Backend already allows all origins:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// backend/src/app.js
|
||||||
|
res.header("Access-Control-Allow-Origin", "*");
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option 2**: Use Ionic's proxy (for dev server):
|
||||||
|
|
||||||
|
**Edit `ionic.config.json`:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"proxies": [
|
||||||
|
{
|
||||||
|
"path": "/api",
|
||||||
|
"proxyUrl": "http://localhost:3069"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Update service:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
endpoint: "/api/profiles"; // Proxied through dev server
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Images Not Loading
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
|
||||||
|
```
|
||||||
|
404 Not Found: http://localhost:8100/assets/images/profile/john.png
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
|
||||||
|
1. **Verify image exists:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls -la src/assets/images/profile/
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Check path in data:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
// Should be relative to assets/images/
|
||||||
|
"pic": {
|
||||||
|
"thumb": "profile/john_thumbnail.png", // ✅ Correct
|
||||||
|
"detail": "/profile/john_detail.png" // ❌ Leading slash breaks it
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Rebuild to copy assets:**
|
||||||
|
```bash
|
||||||
|
ionic build
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Service Worker Issues (Offline Mode)
|
||||||
|
|
||||||
|
**Problem**: Stale data cached, changes not appearing
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clear service worker cache in Chrome DevTools
|
||||||
|
Application → Storage → Clear site data
|
||||||
|
|
||||||
|
# Or disable service worker during development
|
||||||
|
# Comment out service worker registration in index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Ionic CLI Not Found
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
|
||||||
|
```
|
||||||
|
ionic: command not found
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install Ionic CLI globally (included in DevContainer)
|
||||||
|
npm install -g @ionic/cli
|
||||||
|
|
||||||
|
# Verify installation
|
||||||
|
ionic --version
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 Additional Resources
|
||||||
|
|
||||||
|
- **Ionic 3 Documentation**: https://ionicframework.com/docs/v3/
|
||||||
|
- **Angular 5 Documentation**: https://v5.angular.io/docs
|
||||||
|
- **TypeScript Documentation**: https://www.typescriptlang.org/docs/handbook/basic-types.html
|
||||||
|
- **RxJS 5 Documentation**: https://rxjs-dev.firebaseapp.com/api
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔗 Related Documentation
|
||||||
|
|
||||||
|
- [Root README](../README.md) - Project overview and quick start
|
||||||
|
- [Backend README](../backend/README.md) - API server documentation
|
||||||
|
- [API Reference](../backend/API.md) - REST API endpoints
|
||||||
|
- [Database Schema](../backend/SCHEMA.md) - MongoDB collections
|
||||||
|
- [DevContainer Guide](../.devcontainer/README.md) - Development environment
|
||||||
|
- [Deployment Guide](../DEPLOYMENT.md) - Production deployment
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Need Help?** Check the [root troubleshooting section](../README.md#troubleshooting) or backend documentation for API-related issues.
|
||||||
6
app/ionic.config.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "grindd",
|
||||||
|
"app_id": "",
|
||||||
|
"type": "ionic-angular",
|
||||||
|
"integrations": {}
|
||||||
|
}
|
||||||
7172
app/package-lock.json
generated
Normal file
40
app/package.json
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"name": "urge",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"author": "Ionic Framework",
|
||||||
|
"homepage": "http://ionicframework.com/",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"clean": "ionic-app-scripts clean",
|
||||||
|
"build": "ionic-app-scripts build",
|
||||||
|
"lint": "ionic-app-scripts lint",
|
||||||
|
"ionic:build": "ionic-app-scripts build",
|
||||||
|
"ionic:serve": "ionic-app-scripts serve"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@angular/common": "5.0.3",
|
||||||
|
"@angular/compiler": "5.0.3",
|
||||||
|
"@angular/compiler-cli": "5.0.3",
|
||||||
|
"@angular/core": "5.0.3",
|
||||||
|
"@angular/forms": "5.0.3",
|
||||||
|
"@angular/http": "5.0.3",
|
||||||
|
"@angular/platform-browser": "5.0.3",
|
||||||
|
"@angular/platform-browser-dynamic": "5.0.3",
|
||||||
|
"@ionic-native/core": "4.5.3",
|
||||||
|
"@ionic-native/splash-screen": "4.5.3",
|
||||||
|
"@ionic-native/status-bar": "4.5.3",
|
||||||
|
"@ionic/storage": "2.1.3",
|
||||||
|
"ionic-angular": "3.9.2",
|
||||||
|
"ionic-swipe-all": "^1.2.0",
|
||||||
|
"ionicons": "3.0.0",
|
||||||
|
"moment": "^2.21.0",
|
||||||
|
"rxjs": "5.5.2",
|
||||||
|
"sw-toolbox": "3.6.0",
|
||||||
|
"zone.js": "0.8.18"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@ionic/app-scripts": "3.2.4",
|
||||||
|
"typescript": "2.4.2"
|
||||||
|
},
|
||||||
|
"description": "Nick Pfosi's exploration at modern gay app-based meeting and dating"
|
||||||
|
}
|
||||||
22
app/src/app/app.component.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { Platform } from 'ionic-angular';
|
||||||
|
import { StatusBar } from '@ionic-native/status-bar';
|
||||||
|
import { SplashScreen } from '@ionic-native/splash-screen';
|
||||||
|
|
||||||
|
import { TabsPage } from '../pages/tabs/tabs';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
templateUrl: 'app.html'
|
||||||
|
})
|
||||||
|
export class Urnings {
|
||||||
|
rootPage: any = TabsPage;
|
||||||
|
|
||||||
|
constructor(platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen) {
|
||||||
|
platform.ready().then(() => {
|
||||||
|
// Okay, so the platform is ready and our plugins are available.
|
||||||
|
// Here you can do any higher level native things you might need.
|
||||||
|
statusBar.styleDefault();
|
||||||
|
splashScreen.hide();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
1
app/src/app/app.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<ion-nav [root]="rootPage"></ion-nav>
|
||||||
65
app/src/app/app.module.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { NgModule, ErrorHandler } from '@angular/core';
|
||||||
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
import { HttpModule } from '@angular/http';
|
||||||
|
import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
|
||||||
|
import { IonicSwipeAllModule } from 'ionic-swipe-all';
|
||||||
|
import { Urnings } from './app.component';
|
||||||
|
|
||||||
|
import { ChatPage } from '../pages/chat/chat';
|
||||||
|
import { GridPage } from '../pages/grid/grid';
|
||||||
|
import { InformationPage } from '../pages/information/information';
|
||||||
|
import { LightboxPage } from '../pages/lightbox/lightbox';
|
||||||
|
import { MessagesPage } from '../pages/messages/messages';
|
||||||
|
import { ProfilePage } from '../pages/profile/profile';
|
||||||
|
import { TabsPage } from '../pages/tabs/tabs';
|
||||||
|
import { TellYourStoryPage } from '../pages/tell/tell';
|
||||||
|
import { UsersPage } from '../pages/users/users';
|
||||||
|
|
||||||
|
import { StatusBar } from '@ionic-native/status-bar';
|
||||||
|
import { SplashScreen } from '@ionic-native/splash-screen';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
Urnings,
|
||||||
|
ChatPage,
|
||||||
|
InformationPage,
|
||||||
|
GridPage,
|
||||||
|
LightboxPage,
|
||||||
|
MessagesPage,
|
||||||
|
ProfilePage,
|
||||||
|
TabsPage,
|
||||||
|
TellYourStoryPage,
|
||||||
|
UsersPage
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
BrowserModule,
|
||||||
|
HttpModule,
|
||||||
|
IonicSwipeAllModule,
|
||||||
|
IonicModule.forRoot(Urnings, {
|
||||||
|
iconMode: 'ios',
|
||||||
|
modalEnter: 'modal-slide-in',
|
||||||
|
modalLeave: 'modal-slide-out',
|
||||||
|
tabsPlacement: 'bottom',
|
||||||
|
pageTransition: 'ios-transition'
|
||||||
|
})
|
||||||
|
],
|
||||||
|
bootstrap: [IonicApp],
|
||||||
|
entryComponents: [
|
||||||
|
Urnings,
|
||||||
|
ChatPage,
|
||||||
|
InformationPage,
|
||||||
|
GridPage,
|
||||||
|
LightboxPage,
|
||||||
|
MessagesPage,
|
||||||
|
ProfilePage,
|
||||||
|
TabsPage,
|
||||||
|
TellYourStoryPage,
|
||||||
|
UsersPage
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
StatusBar,
|
||||||
|
SplashScreen,
|
||||||
|
{provide: ErrorHandler, useClass: IonicErrorHandler}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class AppModule {}
|
||||||
91
app/src/app/app.scss
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
// http://ionicframework.com/docs/theming/
|
||||||
|
|
||||||
|
|
||||||
|
// App Global Sass
|
||||||
|
// --------------------------------------------------
|
||||||
|
// Put style rules here that you want to apply globally. These
|
||||||
|
// styles are for the entire app and not just one component.
|
||||||
|
// Additionally, this file can be also used as an entry point
|
||||||
|
// to import other Sass files to be included in the output CSS.
|
||||||
|
//
|
||||||
|
// Shared Sass variables, which can be used to adjust Ionic's
|
||||||
|
// default Sass variables, belong in "theme/variables.scss".
|
||||||
|
//
|
||||||
|
// To declare rules for a specific mode, create a child rule
|
||||||
|
// for the .md, .ios, or .wp mode classes. The mode class is
|
||||||
|
// automatically applied to the <body> element in the app.
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'PT Sans', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-toolbar {
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
.toolbar-background {
|
||||||
|
background-color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-button,
|
||||||
|
.toolbar-title {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-title {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
background-color: #191b1c;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
background-color: #1d1e1f;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
|
||||||
|
.item-block .item-inner {
|
||||||
|
border-bottom-color: #333435;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.tabs {
|
||||||
|
|
||||||
|
.tabbar {
|
||||||
|
background-color: #090a0a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-button {
|
||||||
|
|
||||||
|
.tab-button-icon {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[aria-selected=true] {
|
||||||
|
|
||||||
|
.tab-button-icon {
|
||||||
|
color: #fdb315;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 769px) {
|
||||||
|
body {
|
||||||
|
background-color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-app.app-root {
|
||||||
|
height: 480px;
|
||||||
|
left: 50%;
|
||||||
|
position: relative;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate3d(-50%, -50%, 0);
|
||||||
|
width: 320px;
|
||||||
|
}
|
||||||
|
}
|
||||||
8
app/src/app/main.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { enableProdMode } from '@angular/core';
|
||||||
|
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||||
|
|
||||||
|
import { AppModule } from './app.module';
|
||||||
|
|
||||||
|
enableProdMode();
|
||||||
|
|
||||||
|
platformBrowserDynamic().bootstrapModule(AppModule);
|
||||||
BIN
app/src/assets/data/cruises.cje/StoreContent/persistentStore
Normal file
1
app/src/assets/data/cruises.json
Normal file
1
app/src/assets/data/profiles initial.json
Normal file
1
app/src/assets/data/profiles placeholder.json
Normal file
1
app/src/assets/data/profiles.json
Normal file
BIN
app/src/assets/icon/favicon.ico
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 616 KiB After Width: | Height: | Size: 616 KiB |
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 370 KiB After Width: | Height: | Size: 370 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 381 KiB After Width: | Height: | Size: 381 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 469 KiB After Width: | Height: | Size: 469 KiB |
|
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 139 KiB |
|
Before Width: | Height: | Size: 748 KiB After Width: | Height: | Size: 748 KiB |
|
Before Width: | Height: | Size: 143 KiB After Width: | Height: | Size: 143 KiB |
|
Before Width: | Height: | Size: 600 KiB After Width: | Height: | Size: 600 KiB |
|
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 816 KiB After Width: | Height: | Size: 816 KiB |
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 746 KiB After Width: | Height: | Size: 746 KiB |
|
Before Width: | Height: | Size: 221 KiB After Width: | Height: | Size: 221 KiB |
|
Before Width: | Height: | Size: 422 KiB After Width: | Height: | Size: 422 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 445 KiB After Width: | Height: | Size: 445 KiB |
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 358 KiB After Width: | Height: | Size: 358 KiB |
|
Before Width: | Height: | Size: 424 KiB After Width: | Height: | Size: 424 KiB |
|
Before Width: | Height: | Size: 427 KiB After Width: | Height: | Size: 427 KiB |
|
Before Width: | Height: | Size: 466 KiB After Width: | Height: | Size: 466 KiB |
|
Before Width: | Height: | Size: 372 KiB After Width: | Height: | Size: 372 KiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 183 KiB After Width: | Height: | Size: 183 KiB |
|
Before Width: | Height: | Size: 182 KiB After Width: | Height: | Size: 182 KiB |
|
Before Width: | Height: | Size: 194 KiB After Width: | Height: | Size: 194 KiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 153 KiB After Width: | Height: | Size: 153 KiB |
|
Before Width: | Height: | Size: 275 KiB After Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 406 KiB After Width: | Height: | Size: 406 KiB |
|
Before Width: | Height: | Size: 624 KiB After Width: | Height: | Size: 624 KiB |
|
Before Width: | Height: | Size: 573 KiB After Width: | Height: | Size: 573 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
BIN
app/src/assets/imgs/launch-screen-1125x2436.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
app/src/assets/imgs/launch-screen-1242x2208.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
app/src/assets/imgs/launch-screen-640x1136.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
app/src/assets/imgs/launch-screen-750x1334.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
app/src/assets/imgs/logo.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
app/src/assets/imgs/splash.png
Normal file
|
After Width: | Height: | Size: 158 KiB |
57
app/src/index.html
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" dir="ltr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Urnings</title>
|
||||||
|
<meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||||
|
<meta name="format-detection" content="telephone=no">
|
||||||
|
<meta name="msapplication-tap-highlight" content="no">
|
||||||
|
|
||||||
|
<link rel="icon" type="image/x-icon" href="assets/icon/favicon.ico">
|
||||||
|
<link rel="manifest" href="manifest.json">
|
||||||
|
<meta name="theme-color" content="#4e8ef7">
|
||||||
|
|
||||||
|
<!-- add to homescreen for ios -->
|
||||||
|
<link rel="apple-touch-icon" href="assets/imgs/logo.png">
|
||||||
|
<link rel="apple-touch-startup-image" sizes="640x1136" href="assets/imgs/launch-screen-640x1136.png">
|
||||||
|
<link rel="apple-touch-startup-image" sizes="750x1334" href="assets/imgs/launch-screen-750x1334.png">
|
||||||
|
<link rel="apple-touch-startup-image" sizes="1125x2436" href="assets/imgs/launch-screen-1125x2436.png">
|
||||||
|
<link rel="apple-touch-startup-image" sizes="1242x2208" href="assets/imgs/launch-screen-1242x2208.png">
|
||||||
|
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||||
|
<meta name="apple-mobile-web-app-title" content="Urge">
|
||||||
|
|
||||||
|
<!-- cordova.js required for cordova apps (remove if not needed) -->
|
||||||
|
<!--script src="cordova.js"></script-->
|
||||||
|
|
||||||
|
<!-- un-comment this code to enable service worker
|
||||||
|
<script>
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
navigator.serviceWorker.register('service-worker.js')
|
||||||
|
.then(() => console.log('service worker installed'))
|
||||||
|
.catch(err => console.error('Error', err));
|
||||||
|
}
|
||||||
|
</script>-->
|
||||||
|
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=PT+Sans" rel="stylesheet">
|
||||||
|
<link href="build/main.css" rel="stylesheet">
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<!-- Ionic's root component and where the app will load -->
|
||||||
|
<ion-app></ion-app>
|
||||||
|
|
||||||
|
<!-- The polyfills js is generated during the build process -->
|
||||||
|
<script src="build/polyfills.js"></script>
|
||||||
|
|
||||||
|
<!-- The vendor js is generated during the build process
|
||||||
|
It contains all of the dependencies in node_modules -->
|
||||||
|
<script src="build/vendor.js"></script>
|
||||||
|
|
||||||
|
<!-- The main bundle js is generated during the build process -->
|
||||||
|
<script src="build/main.js"></script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
13
app/src/manifest.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "urge",
|
||||||
|
"short_name": "urge",
|
||||||
|
"start_url": "index.html",
|
||||||
|
"display": "standalone",
|
||||||
|
"icons": [{
|
||||||
|
"src": "assets/imgs/logo.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}],
|
||||||
|
"background_color": "#191b1c",
|
||||||
|
"theme_color": "#191b1c"
|
||||||
|
}
|
||||||
39
app/src/pages/chat/chat.html
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons left>
|
||||||
|
<button ion-button icon-only (tap)="closeChat($event)">
|
||||||
|
<ion-icon name="arrow-back"></ion-icon>
|
||||||
|
</button>
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-title
|
||||||
|
><img
|
||||||
|
class="title-profile-avatar"
|
||||||
|
[src]="'/assets/' + this.profile.details.pic.thumb"
|
||||||
|
height="24"
|
||||||
|
width="24"
|
||||||
|
/>
|
||||||
|
{{this.profile.details.name}}</ion-title
|
||||||
|
>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content>
|
||||||
|
<ion-list>
|
||||||
|
<ion-item
|
||||||
|
class="message-bubble"
|
||||||
|
*ngFor="let message of this.profile.messages"
|
||||||
|
[ngClass]="{ 'is-user': (message.isUser == true) }"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
*ngIf="message.image"
|
||||||
|
[src]="'/assets/' + message.image"
|
||||||
|
(press)="showLightbox($event, message.image)"
|
||||||
|
/>
|
||||||
|
<p *ngIf="message.text != ''">{{message.text}}</p>
|
||||||
|
</ion-item>
|
||||||
|
</ion-list>
|
||||||
|
</ion-content>
|
||||||
|
|
||||||
|
<ion-footer>
|
||||||
|
<ion-toolbar> </ion-toolbar>
|
||||||
|
</ion-footer>
|
||||||
48
app/src/pages/chat/chat.scss
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
page-chat {
|
||||||
|
|
||||||
|
.toolbar-title {
|
||||||
|
|
||||||
|
.title-profile-avatar {
|
||||||
|
vertical-align: bottom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
|
||||||
|
.message-bubble {
|
||||||
|
background-color: #fdb315;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
font-family: 'Helvetica Neue', HelveticaNeue, Helvetica, Arial, sans-serif;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin: 1rem 1rem 2.5rem auto;
|
||||||
|
max-width: 75%;
|
||||||
|
overflow: visible;
|
||||||
|
padding: 0.75rem;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&.is-user {
|
||||||
|
background-color: #6fbedf;
|
||||||
|
margin: 1rem auto 2.5rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-inner {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-label {
|
||||||
|
overflow: visible;
|
||||||
|
text-overflow: unset;
|
||||||
|
margin: 0 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: #1d1e1f;
|
||||||
|
white-space: normal;
|
||||||
|
|
||||||
|
&.timestamp {
|
||||||
|
color: #acacac;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
app/src/pages/chat/chat.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { NavController, NavParams } from 'ionic-angular';
|
||||||
|
|
||||||
|
import { LightboxPage } from '../lightbox/lightbox';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'page-chat',
|
||||||
|
templateUrl: 'chat.html'
|
||||||
|
})
|
||||||
|
|
||||||
|
export class ChatPage {
|
||||||
|
|
||||||
|
profile: any;
|
||||||
|
tabNavEl: any;
|
||||||
|
|
||||||
|
constructor(public navCtrl: NavController, private _params: NavParams) {
|
||||||
|
this.profile = this._params.get('profile');
|
||||||
|
this.tabNavEl = document.querySelector('#tab-nav .tabbar');
|
||||||
|
}
|
||||||
|
|
||||||
|
ionViewWillEnter() {
|
||||||
|
this.tabNavEl.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
closeChat(event) {
|
||||||
|
this.navCtrl.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
showLightbox(event, image) {
|
||||||
|
this.navCtrl.push(LightboxPage, {
|
||||||
|
image: image
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
26
app/src/pages/grid/grid.html
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>looking?</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content no-padding>
|
||||||
|
<ion-grid no-padding>
|
||||||
|
<ion-row align-items-stretch>
|
||||||
|
<ion-col
|
||||||
|
col-4
|
||||||
|
class="profile"
|
||||||
|
*ngFor="let current of profiles"
|
||||||
|
(tap)="profileTapped($event, current)"
|
||||||
|
(press)="profilePressed($event, current)"
|
||||||
|
[style.backgroundImage]="getBackgroundThumbnail(current.details.pic)"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="username"
|
||||||
|
[ngClass]="{ 'online': (current.messages?.length > 0) }"
|
||||||
|
>{{current.details.name}}</span
|
||||||
|
>
|
||||||
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
|
</ion-grid>
|
||||||
|
</ion-content>
|
||||||
66
app/src/pages/grid/grid.scss
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
page-grid {
|
||||||
|
|
||||||
|
ion-toolbar {
|
||||||
|
|
||||||
|
.toolbar-title {
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 2.42em;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.29;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
|
||||||
|
.row {
|
||||||
|
|
||||||
|
.col {
|
||||||
|
|
||||||
|
&.profile {
|
||||||
|
background-size: cover;
|
||||||
|
border: 1px solid #000000;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0 0 33% !important;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.username {
|
||||||
|
background-size: cover;
|
||||||
|
bottom: 0.25rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: #ffffff;
|
||||||
|
display: inline-block;
|
||||||
|
left: 0.5rem;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
right: 0.25rem;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
text-shadow: 0 0 3px rgba(0, 0, 0, 1);
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
border: 0.125rem solid #acacac;
|
||||||
|
border-radius: 1rem;
|
||||||
|
bottom: 0.125rem;
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
height: 0.8rem;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: middle;
|
||||||
|
width: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.online {
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background-color: #00ff00;
|
||||||
|
border-color: #00ff00;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
57
app/src/pages/grid/grid.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { Component } from "@angular/core";
|
||||||
|
import { DomSanitizer } from "@angular/platform-browser";
|
||||||
|
import { NavController } from "ionic-angular";
|
||||||
|
|
||||||
|
import { ChatPage } from "../chat/chat";
|
||||||
|
import { ProfileService } from "../../services/profiles";
|
||||||
|
import { ProfilePage } from "../profile/profile";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "page-grid",
|
||||||
|
templateUrl: "grid.html",
|
||||||
|
providers: [ProfileService],
|
||||||
|
})
|
||||||
|
export class GridPage {
|
||||||
|
profiles: any;
|
||||||
|
tabNavEl: any;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public navCtrl: NavController,
|
||||||
|
public profileService: ProfileService,
|
||||||
|
private _sanitizer: DomSanitizer
|
||||||
|
) {
|
||||||
|
profileService.loadVerified().then((data) => {
|
||||||
|
this.profiles = data;
|
||||||
|
console.debug("profiles: ", this.profiles);
|
||||||
|
});
|
||||||
|
this.tabNavEl = document.querySelector("#tab-nav .tabbar");
|
||||||
|
}
|
||||||
|
|
||||||
|
ionViewWillEnter() {
|
||||||
|
this.tabNavEl.style.display = "flex";
|
||||||
|
}
|
||||||
|
|
||||||
|
doTellStory() {
|
||||||
|
// this.navCtrl.push(TellYourStoryPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
getBackgroundThumbnail(pics) {
|
||||||
|
return this._sanitizer.bypassSecurityTrustStyle(
|
||||||
|
"url(/assets/" + pics.thumb + ")"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
profilePressed(event, profile) {
|
||||||
|
if (profile.messages && profile.messages.length) {
|
||||||
|
this.navCtrl.push(ChatPage, {
|
||||||
|
profile: profile,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
profileTapped(event, profile) {
|
||||||
|
this.navCtrl.push(ProfilePage, {
|
||||||
|
profile: profile,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
20
app/src/pages/information/information.html
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons right>
|
||||||
|
<button ion-button icon-only (tap)="close($event)">
|
||||||
|
<ion-icon name="close"></ion-icon>
|
||||||
|
</button>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content padding>
|
||||||
|
<div class="content-box" padding margin>
|
||||||
|
<h3>About this Project</h3>
|
||||||
|
<div class="info-blurb">
|
||||||
|
<p>The app was designed by Nicholas Pfosi and developed by Michael Fitzpatrick, modeled after the popular gay dating app Grindr.</p>
|
||||||
|
<p>Presenting these stories in this form, which is the conduit through which much participation in dating occurs, served multiple purposes. First, it educated the viewer who may not have used Grindr before, how it functions and how it is different from other apps such as Tinder, whereby matching with a person is a prerequisite for conversation. Second, it makes the scope of the project flexible, allowing for the submission of stories from the audience to be slotted into an expandable presentation.</p>
|
||||||
|
<p>Please direct any questions or concerns to Nicholas Pfosi at npfosi@gmail.com</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ion-content>
|
||||||
9
app/src/pages/information/information.scss
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
page-information {
|
||||||
|
|
||||||
|
.content-box {
|
||||||
|
background-color: #ffffff;
|
||||||
|
color: #000000;
|
||||||
|
font-family: 'Helvetica Neue', HelveticaNeue, Helvetica, Arial, sans-serif;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
23
app/src/pages/information/information.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { NavController } from 'ionic-angular';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'page-information',
|
||||||
|
templateUrl: 'information.html',
|
||||||
|
})
|
||||||
|
export class InformationPage {
|
||||||
|
|
||||||
|
tabNavEl: any;
|
||||||
|
|
||||||
|
constructor(public navCtrl: NavController) {
|
||||||
|
this.tabNavEl = document.querySelector('#tab-nav .tabbar');
|
||||||
|
}
|
||||||
|
|
||||||
|
ionViewWillEnter() {
|
||||||
|
this.tabNavEl.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
close(event) {
|
||||||
|
this.navCtrl.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
13
app/src/pages/lightbox/lightbox.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons right>
|
||||||
|
<button ion-button icon-only (tap)="close($event)">
|
||||||
|
<ion-icon name="close"></ion-icon>
|
||||||
|
</button>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content (click)="close($event)">
|
||||||
|
<img class="image-detail" [src]="'https://appsby.fitz.guru/urge/' + this.image">
|
||||||
|
</ion-content>
|
||||||
11
app/src/pages/lightbox/lightbox.scss
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
page-lightbox {
|
||||||
|
|
||||||
|
.image-detail {
|
||||||
|
display: block;
|
||||||
|
height: auto;
|
||||||
|
position: relative;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate3d(0, -50%, 0);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
26
app/src/pages/lightbox/lightbox.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { NavController, NavParams } from 'ionic-angular';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'page-lightbox',
|
||||||
|
templateUrl: 'lightbox.html'
|
||||||
|
})
|
||||||
|
|
||||||
|
export class LightboxPage {
|
||||||
|
|
||||||
|
image: string;
|
||||||
|
tabNavEl: any;
|
||||||
|
|
||||||
|
constructor(public navCtrl: NavController, private _params: NavParams) {
|
||||||
|
this.image = this._params.get('image');
|
||||||
|
this.tabNavEl = document.querySelector('#tab-nav .tabbar');
|
||||||
|
}
|
||||||
|
|
||||||
|
ionViewWillEnter() {
|
||||||
|
this.tabNavEl.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
close(event) {
|
||||||
|
this.navCtrl.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
33
app/src/pages/messages/messages.html
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Urnings</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content no-padding>
|
||||||
|
<ion-list>
|
||||||
|
<ng-container *ngFor="let profile of profiles">
|
||||||
|
<ion-item no-padding *ngIf="profile.messages?.length > 0">
|
||||||
|
<ion-thumbnail
|
||||||
|
padding-left
|
||||||
|
item-start
|
||||||
|
(tap)="profilePictureTapped($event, profile)"
|
||||||
|
>
|
||||||
|
<img [src]="'/assets/' + profile.details.pic.thumb" />
|
||||||
|
</ion-thumbnail>
|
||||||
|
<ion-grid (tap)="interviewTapped($event, profile)">
|
||||||
|
<ion-row nowrap justify-content-between>
|
||||||
|
<ion-col class="username"> {{profile.details.name}} </ion-col>
|
||||||
|
<ion-col
|
||||||
|
class="timestamp"
|
||||||
|
[innerHTML]="getLatestMessageTimestamp(profile.messages)"
|
||||||
|
></ion-col>
|
||||||
|
</ion-row>
|
||||||
|
<ion-row class="latest-message" nowrap>
|
||||||
|
<ion-col [innerHTML]="getLatestMessage(profile.messages)"></ion-col>
|
||||||
|
</ion-row>
|
||||||
|
</ion-grid>
|
||||||
|
</ion-item>
|
||||||
|
</ng-container>
|
||||||
|
</ion-list>
|
||||||
|
</ion-content>
|
||||||
47
app/src/pages/messages/messages.scss
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
page-messages {
|
||||||
|
|
||||||
|
ion-toolbar {
|
||||||
|
|
||||||
|
.toolbar-title {
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 2.42em;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.29;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-header {
|
||||||
|
|
||||||
|
.button {
|
||||||
|
color: #9e9ea8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.col {
|
||||||
|
color: #ffffff;
|
||||||
|
font-family: 'Helvetica Neue', HelveticaNeue, Helvetica, Arial, sans-serif;
|
||||||
|
line-height: 1.5;
|
||||||
|
|
||||||
|
&.username {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.timestamp {
|
||||||
|
font-size: 0.7em;
|
||||||
|
font-style: italic;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.latest-message {
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
|
||||||
|
> .item-block:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
52
app/src/pages/messages/messages.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { NavController } from 'ionic-angular';
|
||||||
|
|
||||||
|
import { ProfileService } from '../../services/profiles';
|
||||||
|
import { ProfilePage } from '../profile/profile';
|
||||||
|
import { ChatPage } from '../chat/chat';
|
||||||
|
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'page-messages',
|
||||||
|
templateUrl: 'messages.html',
|
||||||
|
providers: [ ProfileService ]
|
||||||
|
})
|
||||||
|
export class MessagesPage {
|
||||||
|
|
||||||
|
profiles: any;
|
||||||
|
tabNavEl: any;
|
||||||
|
|
||||||
|
constructor(public navCtrl: NavController, public profileService: ProfileService) {
|
||||||
|
profileService.load().then((data) => {
|
||||||
|
this.profiles = data;
|
||||||
|
});
|
||||||
|
this.tabNavEl = document.querySelector('#tab-nav .tabbar');
|
||||||
|
}
|
||||||
|
|
||||||
|
ionViewWillEnter() {
|
||||||
|
this.tabNavEl.style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
getLatestMessage(messages) {
|
||||||
|
var latest = messages[(messages.length - 1)];
|
||||||
|
var isUser = latest.isUser;
|
||||||
|
return latest.text ? latest.text : '<em>' + (!isUser ? 'Sent ' : '') + 'Photo' + (isUser ? ' Recieved' : '') + '</em>';
|
||||||
|
}
|
||||||
|
|
||||||
|
getLatestMessageTimestamp(messages) {
|
||||||
|
return moment(messages[(messages.length - 1)].timestamp).fromNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
interviewTapped(event, profile) {
|
||||||
|
this.navCtrl.push(ChatPage, {
|
||||||
|
profile: profile
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
profilePictureTapped(event, profile) {
|
||||||
|
this.navCtrl.push(ProfilePage, {
|
||||||
|
profile: profile
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
27
app/src/pages/profile/profile.html
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<ion-content no-padding [style.backgroundImage]="getBackground(profile.details.pic)" (press)="showLightbox($event, profile.details.pic.detail)" on-swipe-left="nextProfile($event)" on-swipe-right="previousProfile($event)">
|
||||||
|
<ion-toolbar class="profile-toolbar">
|
||||||
|
<ion-buttons left>
|
||||||
|
<button ion-button icon-only (tap)="closeProfile($event)">
|
||||||
|
<ion-icon name="arrow-back"></ion-icon>
|
||||||
|
</button>
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-title>{{this.profile.details.name}}</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
|
||||||
|
<button ion-button icon-only clear large (tap)="openChat($event, this.profile)" class="button-chat">
|
||||||
|
<ion-icon name="ios-chatboxes"></ion-icon>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div id="detail-overlay" class="details">
|
||||||
|
<ion-grid>
|
||||||
|
<ion-row nowrap align-items-center justify-content-between>
|
||||||
|
<ion-col col-12 text-center (click)="toggleProfileDetails($event)" class="detail-toggle">
|
||||||
|
<ion-icon name="arrow-down"></ion-icon>
|
||||||
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
|
<ion-row class="about" *ngIf="this.profile.details.about">
|
||||||
|
<ion-col col-12 [innerHTML]="this.profile.details.about"></ion-col>
|
||||||
|
</ion-row>
|
||||||
|
</ion-grid>
|
||||||
|
</div>
|
||||||
|
</ion-content>
|
||||||
72
app/src/pages/profile/profile.scss
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
page-profile {
|
||||||
|
|
||||||
|
ion-content {
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-content {
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-toolbar {
|
||||||
|
border-bottom: 1px solid #ffffff;
|
||||||
|
transition: opacity 250ms 125ms ease-in-out;
|
||||||
|
|
||||||
|
&.hidden {
|
||||||
|
opacity: 0;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-background {
|
||||||
|
background-color: rgba(0, 0, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-button,
|
||||||
|
.toolbar-title {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-chat {
|
||||||
|
bottom: 3rem;
|
||||||
|
color: #fdb315;
|
||||||
|
position: absolute;
|
||||||
|
right: 1.5rem;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-toggle {
|
||||||
|
font-size: 2.5em;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details {
|
||||||
|
bottom: 0;
|
||||||
|
height: 60px;
|
||||||
|
left: 0;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
transition: all 250ms 125ms ease-in-out;
|
||||||
|
|
||||||
|
&.open {
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.about {
|
||||||
|
font-family: 'Helvetica Neue', HelveticaNeue, Helvetica, Arial, sans-serif;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
text-align: right;
|
||||||
|
|
||||||
|
.button-clear {
|
||||||
|
color: #fdb315;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
94
app/src/pages/profile/profile.ts
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import { Component } from "@angular/core";
|
||||||
|
import { DomSanitizer } from "@angular/platform-browser";
|
||||||
|
import { NavController, NavParams } from "ionic-angular";
|
||||||
|
|
||||||
|
import { ChatPage } from "../chat/chat";
|
||||||
|
import { LightboxPage } from "../lightbox/lightbox";
|
||||||
|
import { ProfileService } from "../../services/profiles";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "page-profile",
|
||||||
|
templateUrl: "profile.html",
|
||||||
|
providers: [ProfileService],
|
||||||
|
})
|
||||||
|
export class ProfilePage {
|
||||||
|
detailsOpen: boolean = false;
|
||||||
|
profile: any;
|
||||||
|
tabNavEl: any;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public navCtrl: NavController,
|
||||||
|
public navParams: NavParams,
|
||||||
|
public profileService: ProfileService,
|
||||||
|
private _sanitizer: DomSanitizer
|
||||||
|
) {
|
||||||
|
this.profile = navParams.get("profile");
|
||||||
|
this.tabNavEl = document.querySelector("#tab-nav .tabbar");
|
||||||
|
}
|
||||||
|
|
||||||
|
ionViewWillEnter() {
|
||||||
|
this.tabNavEl.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
closeProfile(event) {
|
||||||
|
this.navCtrl.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
closeProfileDetails(event) {
|
||||||
|
if (this.detailsOpen) {
|
||||||
|
this.detailsOpen = false;
|
||||||
|
document.querySelector(".profile-toolbar").classList.remove("hidden");
|
||||||
|
document.getElementById("detail-overlay").classList.remove("open");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getBackground(pics) {
|
||||||
|
return this._sanitizer.bypassSecurityTrustStyle(
|
||||||
|
"url(/assets/" + pics.detail + ")"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
markFavorite(event, profile) {
|
||||||
|
console.debug("favorite profile", { event: event, profile: profile });
|
||||||
|
}
|
||||||
|
|
||||||
|
nextProfile(event) {
|
||||||
|
this.profile = this.profileService.getNextProfile(this.profile._id);
|
||||||
|
this.navCtrl.setRoot(this.navCtrl.getActive().component);
|
||||||
|
}
|
||||||
|
|
||||||
|
openChat(event, profile) {
|
||||||
|
this.navCtrl.push(ChatPage, {
|
||||||
|
profile: profile,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
openProfileDetails(event) {
|
||||||
|
if (!this.detailsOpen) {
|
||||||
|
this.detailsOpen = true;
|
||||||
|
document.querySelector(".profile-toolbar").classList.add("hidden");
|
||||||
|
document.getElementById("detail-overlay").classList.add("open");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
previousProfile(event) {
|
||||||
|
this.profile = this.profileService.getPreviousProfile(this.profile._id);
|
||||||
|
this.navCtrl.setRoot(this.navCtrl.getActive().component);
|
||||||
|
}
|
||||||
|
|
||||||
|
showLightbox(event, image) {
|
||||||
|
if (event.target.classList.contains("scroll-content")) {
|
||||||
|
this.navCtrl.push(LightboxPage, {
|
||||||
|
image: image,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleProfileDetails(event) {
|
||||||
|
if (!this.detailsOpen) {
|
||||||
|
this.openProfileDetails(event);
|
||||||
|
} else {
|
||||||
|
this.closeProfileDetails(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
app/src/pages/tabs/tabs.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<ion-tabs id="tab-nav" selectedIndex="0">
|
||||||
|
<ion-tab [root]="tab1Root" tabIcon="contacts"></ion-tab>
|
||||||
|
<ion-tab [root]="tab2Root" tabIcon="compass"></ion-tab>
|
||||||
|
<ion-tab [root]="tab3Root" tabIcon="chatboxes"></ion-tab>
|
||||||
|
<ion-tab [root]="tab4Root" tabIcon="information-circle"></ion-tab>
|
||||||
|
</ion-tabs>
|
||||||
21
app/src/pages/tabs/tabs.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
import { GridPage } from '../grid/grid';
|
||||||
|
import { InformationPage } from '../information/information';
|
||||||
|
import { MessagesPage } from '../messages/messages';
|
||||||
|
import { UsersPage } from '../users/users';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
templateUrl: 'tabs.html'
|
||||||
|
})
|
||||||
|
export class TabsPage {
|
||||||
|
|
||||||
|
tab1Root = GridPage;
|
||||||
|
tab2Root = UsersPage;
|
||||||
|
tab3Root = MessagesPage;
|
||||||
|
tab4Root = InformationPage;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
12
app/src/pages/tell/tell.html
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons right>
|
||||||
|
<button ion-button icon-only (tap)="close($event)">
|
||||||
|
<ion-icon name="close"></ion-icon>
|
||||||
|
</button>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content>
|
||||||
|
</ion-content>
|
||||||
27
app/src/pages/tell/tell.scss
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
page-tell-your-story {
|
||||||
|
|
||||||
|
ion-col {
|
||||||
|
|
||||||
|
&.cruise {
|
||||||
|
background-size: cover;
|
||||||
|
border: 1px solid #000000;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0 0 37.5% !important;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.placename {
|
||||||
|
bottom: 0.25rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: #acacac;
|
||||||
|
display: inline-block;
|
||||||
|
left: 0.5rem;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
right: 0.25rem;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
text-shadow: rgba(0, 0, 0, 1);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
app/src/pages/tell/tell.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { NavController } from 'ionic-angular';
|
||||||
|
|
||||||
|
import { ProfileService } from '../../services/profiles';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'page-tell-your-story',
|
||||||
|
templateUrl: 'tell.html',
|
||||||
|
providers: [ ProfileService ]
|
||||||
|
})
|
||||||
|
export class TellYourStoryPage {
|
||||||
|
|
||||||
|
tabNavEl: any;
|
||||||
|
|
||||||
|
constructor(public navCtrl: NavController) {
|
||||||
|
this.tabNavEl = document.querySelector('#tab-nav .tabbar');
|
||||||
|
}
|
||||||
|
|
||||||
|
ionViewWillEnter() {
|
||||||
|
this.tabNavEl.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
close(event) {
|
||||||
|
this.navCtrl.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
20
app/src/pages/users/users.html
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Urnings</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content no-padding>
|
||||||
|
<ion-grid no-padding>
|
||||||
|
<ion-row align-items-stretch>
|
||||||
|
<ion-col col-4 class="profile tell-your-story">
|
||||||
|
<button ion-button clear large icon-only (tap)="doTellStory()">
|
||||||
|
<ion-icon name="md-person-add"></ion-icon>
|
||||||
|
</button>
|
||||||
|
</ion-col>
|
||||||
|
<ion-col col-4 class="profile" *ngFor="let current of profiles" (tap)="profileTapped($event, current)" (press)="profilePressed($event, current)" [style.backgroundImage]="getBackgroundThumbnail(current.details.pic)">
|
||||||
|
<span class="username" [ngClass]="{ 'online': (current.messages?.length > 0) }">{{current.details.name}}</span>
|
||||||
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
|
</ion-grid>
|
||||||
|
</ion-content>
|
||||||
78
app/src/pages/users/users.scss
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
page-users {
|
||||||
|
|
||||||
|
ion-toolbar {
|
||||||
|
|
||||||
|
.toolbar-title {
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 2.42em;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.29;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
|
||||||
|
.row {
|
||||||
|
|
||||||
|
.col {
|
||||||
|
|
||||||
|
&.profile {
|
||||||
|
background-size: cover;
|
||||||
|
border: 1px solid #000000;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0 0 33% !important;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&.tell-your-story {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
button {
|
||||||
|
color: #acacac;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate3d(-50%, -50%, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.username {
|
||||||
|
background-size: cover;
|
||||||
|
bottom: 0.25rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: #ffffff;
|
||||||
|
display: inline-block;
|
||||||
|
left: 0.5rem;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
right: 0.25rem;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
text-shadow: 0 0 3px rgba(0, 0, 0, 1);
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
border: 0.125rem solid #acacac;
|
||||||
|
border-radius: 1rem;
|
||||||
|
bottom: 0.125rem;
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
height: 0.8rem;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: middle;
|
||||||
|
width: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.online {
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background-color: #00ff00;
|
||||||
|
border-color: #00ff00;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
52
app/src/pages/users/users.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { DomSanitizer } from '@angular/platform-browser';
|
||||||
|
import { NavController } from 'ionic-angular';
|
||||||
|
|
||||||
|
import { ChatPage } from '../chat/chat';
|
||||||
|
import { ProfileService } from '../../services/profiles';
|
||||||
|
import { ProfilePage } from '../profile/profile';
|
||||||
|
import { TellYourStoryPage } from '../tell/tell';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'page-users',
|
||||||
|
templateUrl: 'users.html',
|
||||||
|
providers: [ ProfileService ]
|
||||||
|
})
|
||||||
|
export class UsersPage {
|
||||||
|
|
||||||
|
profiles: any;
|
||||||
|
tabNavEl: any;
|
||||||
|
|
||||||
|
constructor(public navCtrl: NavController, public profileService: ProfileService, private _sanitizer: DomSanitizer) {
|
||||||
|
profileService.loadSubmitted().then((data) => {
|
||||||
|
this.profiles = data;
|
||||||
|
});
|
||||||
|
this.tabNavEl = document.querySelector('#tab-nav .tabbar');
|
||||||
|
}
|
||||||
|
|
||||||
|
ionViewWillEnter() {
|
||||||
|
this.tabNavEl.style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
doTellStory() {
|
||||||
|
this.navCtrl.push(TellYourStoryPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
getBackgroundThumbnail(pics) {
|
||||||
|
return this._sanitizer.bypassSecurityTrustStyle('url(https://appsby.fitz.guru/urge/' + pics.thumb + ')');
|
||||||
|
}
|
||||||
|
|
||||||
|
profilePressed(event, profile) {
|
||||||
|
if (profile.messages && profile.messages.length) {
|
||||||
|
this.navCtrl.push(ChatPage, {
|
||||||
|
profile: profile
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
profileTapped(event, profile) {
|
||||||
|
this.navCtrl.push(ProfilePage, {
|
||||||
|
profile: profile,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
31
app/src/service-worker.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Check out https://googlechromelabs.github.io/sw-toolbox/ for
|
||||||
|
* more info on how to use sw-toolbox to custom configure your service worker.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
importScripts('./build/sw-toolbox.js');
|
||||||
|
|
||||||
|
self.toolbox.options.cache = {
|
||||||
|
name: 'ionic-cache'
|
||||||
|
};
|
||||||
|
|
||||||
|
// pre-cache our key assets
|
||||||
|
self.toolbox.precache(
|
||||||
|
[
|
||||||
|
'./build/main.js',
|
||||||
|
'./build/vendor.js',
|
||||||
|
'./build/main.css',
|
||||||
|
'./build/polyfills.js',
|
||||||
|
'index.html',
|
||||||
|
'manifest.json'
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// dynamically cache any other local assets
|
||||||
|
self.toolbox.router.any('/*', self.toolbox.fastest);
|
||||||
|
|
||||||
|
// for any other requests go to the network, cache,
|
||||||
|
// and then only use that cached resource if your user goes offline
|
||||||
|
self.toolbox.router.default = self.toolbox.networkFirst;
|
||||||
98
app/src/services/profiles.ts
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
import { Http } from "@angular/http";
|
||||||
|
import "rxjs/add/operator/map";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ProfileService {
|
||||||
|
endpoint: string = "http://localhost:27017/urnings/profiles";
|
||||||
|
fallback: string = "assets/data/profiles.json";
|
||||||
|
epSubmitted: string = "/submitted";
|
||||||
|
epVerified: string = "/verified";
|
||||||
|
idMap: any = { all: {}, submitted: {}, verified: {} };
|
||||||
|
profiles: any;
|
||||||
|
|
||||||
|
constructor(private http: Http) {
|
||||||
|
this.idMap = { all: {}, submitted: {}, verified: {} };
|
||||||
|
this.profiles = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
load() {
|
||||||
|
if (this.profiles) {
|
||||||
|
return Promise.resolve(this.profiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.doGetRequest(this.endpoint, resolve);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadSubmitted() {
|
||||||
|
if (this.profiles && this.profiles.submitted) {
|
||||||
|
return Promise.resolve(this.profiles.submitted);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.doGetRequest(this.endpoint + this.epSubmitted, resolve, "submitted");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadVerified() {
|
||||||
|
if (this.profiles && this.profiles.verified) {
|
||||||
|
return Promise.resolve(this.profiles.verified);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.doGetRequest(this.endpoint + this.epVerified, resolve, "verified");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
doGetRequest(endpoint, resolve, type = "all") {
|
||||||
|
this.http
|
||||||
|
.get(endpoint)
|
||||||
|
.map((res) => res.json())
|
||||||
|
.subscribe(
|
||||||
|
(data) => {
|
||||||
|
this.profiles = this.profiles || {};
|
||||||
|
this.profiles[type] = data;
|
||||||
|
this.profiles[type].reduce((map, profile, i) => {
|
||||||
|
console.log("profile: ", { map, profile, i });
|
||||||
|
map[profile._id] = i;
|
||||||
|
return map;
|
||||||
|
}, this.idMap[type]);
|
||||||
|
resolve(this.profiles[type]);
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
this.doGetRequest(this.fallback, resolve, type);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getNextProfile(id, type = "all") {
|
||||||
|
var nextIdIndex = this.idMap[type][id] + 1;
|
||||||
|
nextIdIndex = nextIdIndex >= this.profiles[type].length ? 0 : nextIdIndex;
|
||||||
|
return this.profiles[type][nextIdIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
getPreviousProfile(id, type = "all") {
|
||||||
|
var prevIdIndex = this.idMap[type][id] - 1;
|
||||||
|
prevIdIndex =
|
||||||
|
prevIdIndex < 0 ? this.profiles[type].length - 1 : prevIdIndex;
|
||||||
|
return this.profiles[type][prevIdIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
getProfiles() {
|
||||||
|
return this.profiles.all;
|
||||||
|
}
|
||||||
|
|
||||||
|
getProfileById(id) {
|
||||||
|
return this.profiles[this.idMap[id]];
|
||||||
|
}
|
||||||
|
|
||||||
|
getSubmittedProfiles() {
|
||||||
|
return this.profiles.submitted;
|
||||||
|
}
|
||||||
|
|
||||||
|
getVerifiedProfiles() {
|
||||||
|
return this.profiles.verified;
|
||||||
|
}
|
||||||
|
}
|
||||||