Compare commits

..

67 Commits

Author SHA1 Message Date
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
123 changed files with 2105 additions and 224 deletions

34
.devcontainer/Dockerfile Normal file
View File

@@ -0,0 +1,34 @@
FROM node:18-bullseye
# Install additional tools including Ionic CLI
RUN apt-get update && apt-get install -y \
git \
curl \
vim \
mongodb-clients \
&& rm -rf /var/lib/apt/lists/*
# Install global npm packages for development
RUN npm install -g \
@ionic/cli \
@angular/cli \
nodemon \
concurrently \
eslint \
prettier
# Set working directory
WORKDIR /workspaces/PfosiLooking
# Copy package files for dependency caching
COPY package*.json ./
COPY app/package*.json ./app/
COPY backend/package*.json ./backend/
# Install dependencies
RUN npm install
RUN cd app && npm install
RUN cd backend && npm install
# Keep container running
CMD ["sleep", "infinity"]

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",
"shutdownAction": "stopCompose",
"features": {
"ghcr.io/devcontainers/features/node:1": {
"version": "18"
},
"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": true
},
"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": "npm install && cd app && npm install && cd ../backend && npm install",
"postStartCommand": "npm run dev:all"
}

View File

@@ -0,0 +1,70 @@
version: "3.8"
services:
app:
build:
context: ..
dockerfile: .devcontainer/Dockerfile
volumes:
- ../..:/workspaces:cached
- /workspaces/PfosiLooking/app/node_modules
- /workspaces/PfosiLooking/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

@@ -1,184 +1,132 @@
kind: pipeline
type: docker
name: Publish Pipeline
workspace:
path: /drone/looking
name: looking-fullstack
steps:
- name: Publish Image
# Install dependencies for both apps
- name: install-root
image: node:18-alpine
commands:
- npm ci
- name: install-frontend
image: node:18-alpine
commands:
- cd app && npm ci
depends_on:
- install-root
- name: install-backend
image: node:18-alpine
commands:
- cd backend && npm ci
depends_on:
- install-root
# Linting
- name: lint-frontend
image: node:18-alpine
commands:
- cd app && npm run lint
depends_on:
- install-frontend
- name: lint-backend
image: node:18-alpine
commands:
- cd backend && npm run lint
depends_on:
- install-backend
# Testing
- name: test-frontend
image: node:18-alpine
commands:
- cd app && npm run test:ci
depends_on:
- install-frontend
- name: test-backend
image: node:18-alpine
commands:
- cd backend && npm test
depends_on:
- install-backend
# Build
- name: build-frontend
image: node:18-alpine
commands:
- cd app && npm run build
depends_on:
- lint-frontend
- test-frontend
# Docker builds
- name: docker-build-frontend
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>
registry: git.mifi.dev:12023
repo: git.mifi.dev:12023/mifi/pfosi-looking-frontend
username:
from_secret: docker_username
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"
}
from_secret: docker_password
tags:
- latest
- ${DRONE_COMMIT_SHA:0:8}
dockerfile: app/Dockerfile
context: app
depends_on:
- build-frontend
when:
status:
- success
- failure
branch:
- master
- main
volumes:
- name: dockerconfig
host:
path: /volume1/docker/dockerconfig.json
- name: dockersock
host:
path: /var/run/docker.sock
- name: docker-build-backend
image: plugins/docker
settings:
registry: git.mifi.dev:12023
repo: git.mifi.dev:12023/mifi/pfosi-looking-backend
username:
from_secret: docker_username
password:
from_secret: docker_password
tags:
- latest
- ${DRONE_COMMIT_SHA:0:8}
dockerfile: backend/Dockerfile
context: backend
depends_on:
- lint-backend
- test-backend
when:
branch:
- master
- main
# Deploy both services
- name: deploy-stack
image: curlimages/curl
commands:
- curl -X POST $PORTAINER_WEBHOOK_URL
environment:
PORTAINER_WEBHOOK_URL:
from_secret: portainer_webhook_fullstack
depends_on:
- docker-build-frontend
- docker-build-backend
when:
branch:
- master
- main
trigger:
branch:
- master
- main
- 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
- pull_request

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/

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

6
app/ionic.config.json Normal file
View File

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

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.1.8",
"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

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,25 @@
<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]="'https://appsby.fitz.guru/urge/' + 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]="'https://appsby.fitz.guru/urge/' + 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,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';
@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(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,
});
}
}

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,28 @@
<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]="'https://appsby.fitz.guru/urge/' + 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,89 @@
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(https://appsby.fitz.guru/urge/' + 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,97 @@
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';
@Injectable()
export class ProfileService {
endpoint: string = 'https://api.fitz.guru/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 = {};
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) => {
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;
}
}

View File

@@ -0,0 +1,88 @@
// Ionic Variables and Theming. For more info, please see:
// http://ionicframework.com/docs/theming/
// Font path is used to include ionicons,
// roboto, and noto sans fonts
$font-path: "../assets/fonts";
// The app direction is used to include
// rtl styles in your app. For more info, please see:
// http://ionicframework.com/docs/theming/rtl-support/
$app-direction: ltr;
@import "ionic.globals";
// Shared Variables
// --------------------------------------------------
// To customize the look and feel of this app, you can override
// the Sass variables found in Ionic's source scss files.
// To view all the possible Ionic variables, see:
// http://ionicframework.com/docs/theming/overriding-ionic-variables/
// Named Color Variables
// --------------------------------------------------
// Named colors makes it easy to reuse colors on various components.
// It's highly recommended to change the default colors
// to match your app's branding. Ionic uses a Sass map of
// colors so you can add, rename and remove colors as needed.
// The "primary" color is the only required color in the map.
$colors: (
primary: #488aff,
secondary: #32db64,
danger: #f53d3d,
light: #f4f4f4,
dark: #222
);
// App iOS Variables
// --------------------------------------------------
// iOS only Sass variables can go here
// App Material Design Variables
// --------------------------------------------------
// Material Design only Sass variables can go here
// App Windows Variables
// --------------------------------------------------
// Windows only Sass variables can go here
// App Theme
// --------------------------------------------------
// Ionic apps can have different themes applied, which can
// then be future customized. This import comes last
// so that the above variables are used and Ionic's
// default are overridden.
@import "ionic.theme.default";
// Ionicons
// --------------------------------------------------
// The premium icon font for Ionic. For more info, please see:
// http://ionicframework.com/docs/ionicons/
@import "ionic.ionicons";
// Fonts
// --------------------------------------------------
@import "roboto";
@import "noto-sans";

28
app/tsconfig.json Normal file
View File

@@ -0,0 +1,28 @@
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"declaration": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": [
"dom",
"es2015"
],
"module": "es2015",
"moduleResolution": "node",
"sourceMap": true,
"target": "es5"
},
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules",
"src/**/*.spec.ts",
"src/**/__tests__/*.ts"
],
"compileOnSave": false,
"atom": {
"rewriteTsconfig": false
}
}

11
app/tslint.json Normal file
View File

@@ -0,0 +1,11 @@
{
"rules": {
"no-duplicate-variable": true,
"no-unused-variable": [
true
]
},
"rulesDirectory": [
"node_modules/tslint-eslint-rules/dist/rules"
]
}

View File

@@ -1,7 +1,11 @@
FROM node:18
FROM node:latest
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
EXPOSE 3069
CMD ["npm", "start"]

View File

@@ -0,0 +1,21 @@
version: '3.8'
services:
backend:
build: .
ports:
- "3069:3069"
depends_on:
- mongo
environment:
- MONGODB_URI=mongodb://mongo:27017/urge
mongo:
image: mongo:4.4
ports:
- "27017:27017"
volumes:
- mongo-data:/data/db
volumes:
mongo-data:

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

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