Compare commits

...

70 Commits

Author SHA1 Message Date
2a5de6df36 Resolve start-up errors 2025-12-28 14:12:08 -03:00
7fa78e870b Documentation 2025-12-28 13:52:25 -03:00
f17c0d08a1 Little tweaks to make it work... In a devcontainer anyway.... 2025-07-25 21:32:20 -03:00
9eb293ccb1 Add monorepo configuration and development environment 2025-07-25 19:14:05 -03:00
cedf771f16 Merge frontend repository into app/ subdirectory 2025-07-25 19:12:51 -03:00
bb2e4f2a42 Merge remote-tracking branch 'app-origin/master' 2025-07-25 19:11:32 -03:00
1bd4ca0d98 Move backend files to backend/ subdirectory 2025-07-25 19:09:35 -03:00
d04304b573 Wrong mongo version for dev 2025-04-27 10:51:33 -04:00
e9fa0ea685 Oops 2025-04-27 02:46:35 -04:00
45a0be4210 quick fixes 2025-04-27 02:33:06 -04:00
f4df2431ad Pipeline updates 2025-04-27 01:54:48 -04:00
2bfec1640f Updated Dockerfile and docker-compose 2025-04-27 00:23:26 -04:00
281d35d63a Drone 2024-12-03 23:10:13 -05:00
fcb35f1e40 Merge branch 'release/2.0.5' 2018-05-29 19:13:49 -04:00
048c2edb9b - Grid fixes 2018-05-29 19:13:26 -04:00
f7096108f0 Merge branch 'release/2.0.4' 2018-05-29 01:32:36 -04:00
de8aa503ba Merge tag '2.0.4' into develop
no message
2018-05-29 01:32:36 -04:00
d5ca679865 - More profile fixes 2018-05-29 01:31:59 -04:00
9362d110a5 Merge branch 'release/2.0.3' 2018-05-29 01:11:46 -04:00
2e94702ec2 Merge tag '2.0.3' into develop
no message
2018-05-29 01:11:46 -04:00
709caf208a - Profiles service mapping 2018-05-29 01:11:27 -04:00
70fe066cdd Merge branch 'release/2.0.2' 2018-05-29 01:08:07 -04:00
73f456ed00 Merge tag '2.0.2' into develop
no message
2018-05-29 01:08:07 -04:00
c9431f62ef - profiles service fixes 2018-05-29 01:07:25 -04:00
b91f7fcfba Merge branch 'release/2.0.1' 2018-05-29 00:37:55 -04:00
58b25b0a44 Merge tag '2.0.1' into develop
no message
2018-05-29 00:37:55 -04:00
01ed427b54 - Fix for verified profiles 2018-05-29 00:37:30 -04:00
fbaaf60d3f Merge branch 'release/2.0' 2018-05-29 00:31:52 -04:00
8fd37ec556 Merge tag '2.0' into develop
UI Revisions
2018-05-29 00:31:52 -04:00
8f5ec53ac4 - UI Revisions 2018-05-29 00:30:49 -04:00
a7833a9314 Grid background size 2018-05-09 09:06:49 -04:00
78399b5d1d no message 2018-03-09 04:05:50 -05:00
e387be003b no message 2018-03-09 03:57:18 -05:00
e69a89e5d3 no message 2018-03-09 03:54:43 -05:00
8ef1febccc no message 2018-03-09 03:52:53 -05:00
81d580ca99 no message 2018-03-09 03:46:18 -05:00
a845797b3d no message 2018-03-09 02:38:37 -05:00
2366be8432 no message 2018-03-09 02:37:00 -05:00
b415164852 no message 2018-03-09 02:12:15 -05:00
c1b073c707 no message 2018-03-09 01:54:23 -05:00
b67b88e916 no message 2018-03-09 00:39:47 -05:00
df39029fd2 no message 2018-03-09 00:26:45 -05:00
0b27ee4a9b no message 2018-03-09 00:24:28 -05:00
87feefc139 no message 2018-03-09 00:21:13 -05:00
4f23b2e657 no message 2018-03-08 14:48:48 -05:00
0cf95d2cd4 Added cruising pages support 2018-03-08 14:41:45 -05:00
9fcaef1068 - Splash screens 2018-03-08 03:08:16 -05:00
51f4f7499c no message 2018-03-08 01:08:01 -05:00
87d5b85c55 TS errors 2018-03-08 00:53:54 -05:00
996b4097fd More profiles, styling tweaks 2018-03-08 00:50:42 -05:00
f21238b61e no message 2018-03-07 03:30:22 -05:00
80af0530c9 no message 2018-03-07 03:15:38 -05:00
0e4e0ce224 Styling tweaks 2018-03-07 02:50:58 -05:00
f67ab11ec7 no message 2018-03-07 01:13:33 -05:00
8acdc92689 swap chat layout 2018-03-07 01:08:59 -05:00
99259489ab Quick fixes 2018-03-07 00:54:57 -05:00
396078240b no message 2018-03-07 00:53:00 -05:00
4c67666c15 no message 2018-03-07 00:42:49 -05:00
df72c33705 Appifying 2018-03-07 00:39:17 -05:00
164b1e5197 Hmmm 2018-03-07 00:15:27 -05:00
7d5bf0f7ee Making it match the real thing... Well, more so than before... 2018-03-07 00:05:54 -05:00
6e756e0792 Merge branch 'master' of honey.fitz.guru:urge-app 2018-03-06 21:21:42 -05:00
ca376559db no message 2018-03-06 21:21:33 -05:00
94078ad1d5 Fixes and initial styling for chat 2018-03-06 18:33:11 -05:00
57f42584de no message 2018-03-06 03:17:15 -05:00
e3a9e4badc no message 2018-03-06 03:12:07 -05:00
263aae54b4 no message 2018-03-06 01:28:54 -05:00
50c7ba87ef - Wiring up for lifeeeeee 2018-03-06 00:52:51 -05:00
33cf657c70 Initial commit 2018-03-02 02:59:55 -05:00
30518e56d4 Initial commit 2018-02-15 20:40:34 -05:00
172 changed files with 22280 additions and 5250 deletions

43
.devcontainer/Dockerfile Normal file
View 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
View 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
View 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

View 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.'"
}

View 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

View 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"

View File

@@ -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
View File

@@ -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

File diff suppressed because it is too large Load Diff

640
README.md Normal file
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,6 @@
{
"name": "grindd",
"app_id": "",
"type": "ionic-angular",
"integrations": {}
}

7172
app/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

40
app/package.json Normal file
View 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"
}

View 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
View File

@@ -0,0 +1 @@
<ion-nav [root]="rootPage"></ion-nav>

65
app/src/app/app.module.ts Normal file
View 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
View 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
View 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);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 616 KiB

After

Width:  |  Height:  |  Size: 616 KiB

View File

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 141 KiB

View File

Before

Width:  |  Height:  |  Size: 370 KiB

After

Width:  |  Height:  |  Size: 370 KiB

View File

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

View File

Before

Width:  |  Height:  |  Size: 381 KiB

After

Width:  |  Height:  |  Size: 381 KiB

View File

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

View File

Before

Width:  |  Height:  |  Size: 469 KiB

After

Width:  |  Height:  |  Size: 469 KiB

View File

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 139 KiB

View File

Before

Width:  |  Height:  |  Size: 748 KiB

After

Width:  |  Height:  |  Size: 748 KiB

View File

Before

Width:  |  Height:  |  Size: 143 KiB

After

Width:  |  Height:  |  Size: 143 KiB

View File

Before

Width:  |  Height:  |  Size: 600 KiB

After

Width:  |  Height:  |  Size: 600 KiB

View File

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 130 KiB

View File

Before

Width:  |  Height:  |  Size: 816 KiB

After

Width:  |  Height:  |  Size: 816 KiB

View File

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 120 KiB

View File

Before

Width:  |  Height:  |  Size: 746 KiB

After

Width:  |  Height:  |  Size: 746 KiB

View File

Before

Width:  |  Height:  |  Size: 221 KiB

After

Width:  |  Height:  |  Size: 221 KiB

View File

Before

Width:  |  Height:  |  Size: 422 KiB

After

Width:  |  Height:  |  Size: 422 KiB

View File

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

View File

Before

Width:  |  Height:  |  Size: 445 KiB

After

Width:  |  Height:  |  Size: 445 KiB

View File

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 117 KiB

View File

Before

Width:  |  Height:  |  Size: 358 KiB

After

Width:  |  Height:  |  Size: 358 KiB

View File

Before

Width:  |  Height:  |  Size: 424 KiB

After

Width:  |  Height:  |  Size: 424 KiB

View File

Before

Width:  |  Height:  |  Size: 427 KiB

After

Width:  |  Height:  |  Size: 427 KiB

View File

Before

Width:  |  Height:  |  Size: 466 KiB

After

Width:  |  Height:  |  Size: 466 KiB

View File

Before

Width:  |  Height:  |  Size: 372 KiB

After

Width:  |  Height:  |  Size: 372 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

Before

Width:  |  Height:  |  Size: 183 KiB

After

Width:  |  Height:  |  Size: 183 KiB

View File

Before

Width:  |  Height:  |  Size: 182 KiB

After

Width:  |  Height:  |  Size: 182 KiB

View File

Before

Width:  |  Height:  |  Size: 194 KiB

After

Width:  |  Height:  |  Size: 194 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

View File

Before

Width:  |  Height:  |  Size: 153 KiB

After

Width:  |  Height:  |  Size: 153 KiB

View File

Before

Width:  |  Height:  |  Size: 275 KiB

After

Width:  |  Height:  |  Size: 275 KiB

View File

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 75 KiB

View File

Before

Width:  |  Height:  |  Size: 406 KiB

After

Width:  |  Height:  |  Size: 406 KiB

View File

Before

Width:  |  Height:  |  Size: 624 KiB

After

Width:  |  Height:  |  Size: 624 KiB

View File

Before

Width:  |  Height:  |  Size: 573 KiB

After

Width:  |  Height:  |  Size: 573 KiB

View File

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

57
app/src/index.html Normal file
View 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
View 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"
}

View 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>

View 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;
}
}
}
}
}

View 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
});
}
}

View File

@@ -0,0 +1,15 @@
<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" *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>

View 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;
}
}
}
}
}
}
}
}

View 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,
});
}
}

View 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>

View 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;
}
}

View 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();
}
}

View 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>

View File

@@ -0,0 +1,11 @@
page-lightbox {
.image-detail {
display: block;
height: auto;
position: relative;
top: 50%;
transform: translate3d(0, -50%, 0);
width: 100%;
}
}

View 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();
}
}

View 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>

View 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;
}
}
}

View 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
});
}
}

View 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>

View 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;
}
}
}
}

View 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);
}
}
}

View 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>

View 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() {
}
}

View 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>

View 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;
}
}
}
}

View 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();
}
}

View 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>

View 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;
}
}
}
}
}
}
}
}

View 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
View 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;

View 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;
}
}

Some files were not shown because too many files have changed in this diff Show More