Compare commits
208 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e9948c1f5c | |||
| 568628f0a2 | |||
| abf46c6885 | |||
| 01d8598f59 | |||
| 1095b781cd | |||
| 1569d3c7ce | |||
| 652b35f5bd | |||
| 57a8af7ef8 | |||
| e8be631182 | |||
| 1dda82790c | |||
| c47ea53b50 | |||
| d4d994fe26 | |||
| 2dc2f380ff | |||
| 2604fc2034 | |||
| 7db4b2a2ee | |||
| 3ec8263d15 | |||
| d0c14cf1ab | |||
| a078c9adec | |||
| 3caa4dcde7 | |||
| 860c6d1075 | |||
| cccfd98736 | |||
| c37d521d0e | |||
| 1de58de8de | |||
| fc1adb5662 | |||
| b5e36872b6 | |||
| 1c4183dd85 | |||
| 535302c893 | |||
| 3a28cc392a | |||
| 63321e0550 | |||
| 4789da2c43 | |||
| 1498e2615c | |||
| 3aad970fd6 | |||
| c5821737b2 | |||
| 75a79711aa | |||
| f4447fd2dc | |||
| 6be9999d44 | |||
| 5dd4bffdc5 | |||
|
14dfdf8735
|
|||
| 19eed3810d | |||
| d5e0f3ca79 | |||
|
b868d67cd1
|
|||
|
098a0f469d
|
|||
|
718c489042
|
|||
|
94c8b900a9
|
|||
| 830d7f75e1 | |||
| 3ec8386a04 | |||
|
a1e1f28137
|
|||
| f6873efcdd | |||
| c4be27dada | |||
| b319c0e6e4 | |||
| c63383ba89 | |||
| df62f8f846 | |||
| 17d895cab0 | |||
| 86cfdfb875 | |||
|
370c7c39f5
|
|||
|
5dd891c5b2
|
|||
|
7bf163d77c
|
|||
|
9feead28cd
|
|||
|
323b7b555a
|
|||
|
d09a527c9d
|
|||
| 25a112bc3f | |||
|
b7ec08f5ef
|
|||
|
8bca33a039
|
|||
|
6179f8d4c2
|
|||
|
e08f4591c6
|
|||
| bc04d35909 | |||
|
5d8a29d19c
|
|||
|
b52c17162d
|
|||
|
3a555a690c
|
|||
|
d81ba003e0
|
|||
| c4292fdd33 | |||
| 13d04ad364 | |||
| 525fe38b73 | |||
| e1e9fb877a | |||
| b826f1f6a4 | |||
| e2a8c75d46 | |||
| a4fc9bdce0 | |||
| 1a6700a983 | |||
| 2571b8cc73 | |||
| 88b7202069 | |||
| 456c8bc1af | |||
| c36b33402b | |||
| abdbe69fd9 | |||
| 81da8f5b2d | |||
| b05bebfd07 | |||
| 947041b62b | |||
|
e50062f615
|
|||
|
ced20c16c5
|
|||
|
a2ae2b2bea
|
|||
|
f82b04d36c
|
|||
|
fdb22a5307
|
|||
| f92bf2510a | |||
|
965db39ad0
|
|||
| 584510d0f3 | |||
| 981d17166e | |||
|
12cfc6f6a0
|
|||
|
2ec3b93190
|
|||
|
961e2b4587
|
|||
|
eaadade0ed
|
|||
|
564524772e
|
|||
|
b2c21d0782
|
|||
|
fa29041489
|
|||
|
296f21a082
|
|||
| ed3449a1f7 | |||
| 9f84ee233d | |||
| 1396d03b76 | |||
| ba0575f315 | |||
| 644206635f | |||
| 2b6bc641d4 | |||
| 0962df2187 | |||
|
7212a532f6
|
|||
| 85cd7c20f4 | |||
| 844bb14040 | |||
| 2e0ac0ba0f | |||
|
ea3647ecd3
|
|||
|
2796172d30
|
|||
|
0c8ca18326
|
|||
|
b1009b186e
|
|||
| 72ae128975 | |||
|
9802ea8096
|
|||
|
aa49341937
|
|||
|
29d6833e42
|
|||
|
d26e7d4290
|
|||
|
1f22895904
|
|||
|
319f066edf
|
|||
|
c77f16d3fc
|
|||
| c8d7e40203 | |||
| e1572cd84a | |||
| 1e5f462abd | |||
| 973096d480 | |||
| 5aa1c888e1 | |||
| 594c09ba65 | |||
| a883e3112e | |||
| b87c6ee600 | |||
| 94b8591643 | |||
| b7a30c3d6d | |||
| daa9de4142 | |||
| 08b57dc2b2 | |||
| e179e8cdef | |||
| 57e61e362a | |||
|
0042244b80
|
|||
| e8dfc7bf88 | |||
| 72bcccfe2f | |||
| 2c457ab531 | |||
| 1120f5eb6e | |||
| f16b77c94b | |||
| 62a813d0ad | |||
| 151de4ebfd | |||
| 199c283b0e | |||
|
60bdf36274
|
|||
|
a7eb9b942b
|
|||
|
7cd1e9b5da
|
|||
|
afd5dbaa9f
|
|||
|
eeb6b856e6
|
|||
|
9ee8bf3376
|
|||
|
848d588bf4
|
|||
| 7ca4b62848 | |||
| 8cd5fd8783 | |||
| afd72ec72b | |||
| 0895e93f6c | |||
| 67ec566728 | |||
|
8737d10a1e
|
|||
|
d9bca7f1ff
|
|||
|
4b9b46f10d
|
|||
|
bafcd88fa1
|
|||
| ac7317a0b7 | |||
| 034b217916 | |||
| ef3f7fa174 | |||
|
17d2ce436a
|
|||
|
6dbc0a2e8e
|
|||
|
43e9045b0a
|
|||
|
5ca9c4222c
|
|||
|
5e8a5c89b3
|
|||
| dc5469fc70 | |||
|
ac19919c51
|
|||
| 7d2b653953 | |||
| 6dea6cc168 | |||
| 75e3476d48 | |||
| c73f85a411 | |||
| 5aba1549d4 | |||
| 3f68e43efd | |||
| b24a2b5254 | |||
| b38f12a6ab | |||
| b07a333459 | |||
| 13e5f319c7 | |||
| 4391a072cc | |||
| 66c9de922d | |||
| 245ae616b1 | |||
| 3afbe17a21 | |||
| 8d3283ef04 | |||
| 8862f7b94c | |||
| 6e5af78586 | |||
| ca731147f2 | |||
| a8dc4d8460 | |||
| 6a0d3f3834 | |||
| 04cdc500b2 | |||
| 8cd7cafdb5 | |||
| ec2fc15a3f | |||
| d7a2382cb5 | |||
| 69d5bfe9a9 | |||
| c555a59cf8 | |||
| d20aecdbe8 | |||
| 57a8415e52 | |||
|
f19b09c5fd
|
|||
|
c6056c385b
|
|||
|
7420cc63fb
|
|||
|
c528c6bacf
|
|||
|
daedd0b068
|
@@ -1,29 +0,0 @@
|
||||
name: Frontend Next.Js Build Validation
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
|
||||
jobs:
|
||||
nextjs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 21.6.2
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install frontend-next/
|
||||
|
||||
- name: Lint
|
||||
run: npm --prefix frontend-next/ run lint
|
||||
|
||||
- name: Build Frontend
|
||||
run: npm --prefix frontend-next/ run build
|
||||
@@ -0,0 +1,27 @@
|
||||
name: JSDoc to GH Pages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Build
|
||||
uses: andstor/jsdoc-action@v1
|
||||
with:
|
||||
source_dir: ./frontend-next
|
||||
output_dir: ./jsdoc
|
||||
recurse: true
|
||||
template: minami
|
||||
|
||||
- name: Deploy
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./jsdoc
|
||||
@@ -1,3 +1,10 @@
|
||||
# Android Build
|
||||
/frontend-next/android
|
||||
|
||||
# Firebase Stuff
|
||||
firebase-admin-key.json
|
||||
firebase*.json
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
@@ -127,4 +134,7 @@ dist
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
.pnp.*
|
||||
|
||||
# Am I the only mac user
|
||||
**/.DS_Store
|
||||
@@ -1,21 +1,25 @@
|
||||
# ChatMaps
|
||||

|
||||
Main repo for ChatMaps, our COS420 Project.
|
||||
|
||||
ChatMaps is a web-based social networking service that allows users to connect to others in their local geographic area. It will implement an interactable mapping utility to show general user locations relative to other users, as well as a chat room feature that allows users to start public conversations based on a specified topic. ChatMaps is primarily intended for use in densely populated areas, such as college campuses or metropolitan areas, so people of similar interests can start conversations. The goal of this project is to create a web app that plots locations, gives a radius of the local area, and connects users into different topic-based chat rooms.
|
||||
ChatMaps is a web-based social networking service that allows users to connect to others in their local geographic area. It implements an interactable mapping utility to show general user locations relative to other users, as well as a chat room feature that allows users to start public conversations based on a specified topic. ChatMaps is primarily intended for use in densely populated areas, such as college campuses or metropolitan areas, so people of similar interests can start conversations. The goal of this project is to create a web app that plots locations, gives a radius of the local area, and connects users into different topic-based chat rooms.
|
||||
|
||||
This service will implement user login and profiles, allowing users to add each other as friends and start private conversations. There will be several default chat rooms of varying topics, but users will also have the ability to create their own topics that will be viewable by other users. For example, a user at the University of Maine could create a joinable chat room titled “COS420”, which would be visible to others near this campus.
|
||||
This service implements user login and profiles, allowing users to add each other as friends and start private conversations. There are several default chat rooms of varying topics, but users also have the ability to create their own topics that will be viewable by other users. For example, a user at the University of Maine could create a joinable chat room titled “COS420”, which would be visible to others near this campus.
|
||||
|
||||
This app shares some similarities to other social networks that implement location-based content. ChatMaps’ novel approach is to utilize user location to facilitate real-time communication with others within a given radius.
|
||||
|
||||
The live version of this app can be found at:
|
||||
|
||||
http://chatmaps.nicholaspease.com
|
||||
https://chatma.ps/
|
||||
|
||||
A local version can be run with:
|
||||
|
||||
cd frontend-next/
|
||||
|
||||
npm run dev
|
||||
npm install
|
||||
|
||||
npm run build
|
||||
|
||||
npm run start
|
||||
|
||||
then navigating to:
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
node_modules/
|
||||
@@ -1,16 +0,0 @@
|
||||
## Server Update Hook
|
||||
The purpose of this file is to enable automatic server updates when a pull request to the main branch succeeds. This is accomplished via a workflow sent from Github to the server which then reloads both itself and the frontend.
|
||||
|
||||
## API Endpoints
|
||||
Base Url: ```https://chatmaps.nicholaspease.com/api/v1/```
|
||||
|
||||
|Route|Method|Response Type|Use|Responses|
|
||||
|-----|------|-------------|---|---------|
|
||||
|```/``` |GET |Plain Text |Heartbeat|200 - "OK"|
|
||||
|```/deploy```|POST|Plain Text |Server Update Trigger|200 - "OK" - Server Online / Updated
|
||||
|
||||
## Files
|
||||
|File|Purpose|
|
||||
|----|-------|
|
||||
|api.js|NodeJS API using Express|
|
||||
|README.md|API Reference|
|
||||
@@ -1,32 +0,0 @@
|
||||
// Physical Server Update Hook
|
||||
// Imports
|
||||
// Requires express, body-parser, child_process
|
||||
const express = require("express")
|
||||
const bodyParser = require("body-parser")
|
||||
const { exec } = require("child_process")
|
||||
|
||||
// Start the express app and initialize
|
||||
const app = express()
|
||||
app.use(bodyParser.urlencoded({ extended: false }));
|
||||
app.use(bodyParser.json()); // This processes all the POST data as JSON
|
||||
|
||||
// Uses nodejs and express as "basic" backend server
|
||||
// Recieves webhook from Github at https://chatmaps.nicholaspease.com/
|
||||
app.post("/api/v1/deploy", function (req, res) {
|
||||
// Webhook returns all pull request actions
|
||||
// Limit to "closed", "merged", and the target branch being "main"
|
||||
if (req.body.action == "closed" && req.body.pull_request.merged == true && req.body.pull_request.base.ref == "main") {
|
||||
// (re)Start all the systemd files
|
||||
exec("systemctl restart frontend-next.service", (error, stdout, stderr) => {});
|
||||
exec("systemctl restart server_update_hook.service", (error, stdout, stderr) => {});
|
||||
}
|
||||
res.send("OK")
|
||||
})
|
||||
|
||||
// Generic endpoint, useful to test if updater is alive
|
||||
app.get('/api/v1', (req, res) => {
|
||||
res.send('OK')
|
||||
})
|
||||
|
||||
// Server runs on port 8000, exposed on server at /api/v1
|
||||
app.listen(8000)
|
||||
@@ -1,728 +0,0 @@
|
||||
{
|
||||
"name": "backend",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"body-parser": "^1.20.2",
|
||||
"express": "^4.18.2"
|
||||
}
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
|
||||
"dependencies": {
|
||||
"mime-types": "~2.1.34",
|
||||
"negotiator": "0.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.20.2",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
|
||||
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
"content-type": "~1.0.5",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"destroy": "1.2.0",
|
||||
"http-errors": "2.0.0",
|
||||
"iconv-lite": "0.4.24",
|
||||
"on-finished": "2.4.1",
|
||||
"qs": "6.11.0",
|
||||
"raw-body": "2.5.2",
|
||||
"type-is": "~1.6.18",
|
||||
"unpipe": "1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8",
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/bytes": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
|
||||
"integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0",
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"set-function-length": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
|
||||
"dependencies": {
|
||||
"safe-buffer": "5.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/content-type": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
|
||||
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
|
||||
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/define-data-property": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
||||
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/destroy": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
||||
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
|
||||
"engines": {
|
||||
"node": ">= 0.8",
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
||||
},
|
||||
"node_modules/encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
|
||||
},
|
||||
"node_modules/etag": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/express": {
|
||||
"version": "4.18.2",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
|
||||
"integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.8",
|
||||
"array-flatten": "1.1.1",
|
||||
"body-parser": "1.20.1",
|
||||
"content-disposition": "0.5.4",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "0.5.0",
|
||||
"cookie-signature": "1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"finalhandler": "1.2.0",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "2.0.0",
|
||||
"merge-descriptors": "1.0.1",
|
||||
"methods": "~1.1.2",
|
||||
"on-finished": "2.4.1",
|
||||
"parseurl": "~1.3.3",
|
||||
"path-to-regexp": "0.1.7",
|
||||
"proxy-addr": "~2.0.7",
|
||||
"qs": "6.11.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"safe-buffer": "5.2.1",
|
||||
"send": "0.18.0",
|
||||
"serve-static": "1.15.0",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": "2.0.1",
|
||||
"type-is": "~1.6.18",
|
||||
"utils-merge": "1.0.1",
|
||||
"vary": "~1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/express/node_modules/body-parser": {
|
||||
"version": "1.20.1",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
|
||||
"integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
"content-type": "~1.0.4",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"destroy": "1.2.0",
|
||||
"http-errors": "2.0.0",
|
||||
"iconv-lite": "0.4.24",
|
||||
"on-finished": "2.4.1",
|
||||
"qs": "6.11.0",
|
||||
"raw-body": "2.5.1",
|
||||
"type-is": "~1.6.18",
|
||||
"unpipe": "1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8",
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/express/node_modules/raw-body": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
|
||||
"integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
"http-errors": "2.0.0",
|
||||
"iconv-lite": "0.4.24",
|
||||
"unpipe": "1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/finalhandler": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
|
||||
"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
|
||||
"dependencies": {
|
||||
"debug": "2.6.9",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"on-finished": "2.4.1",
|
||||
"parseurl": "~1.3.3",
|
||||
"statuses": "2.0.1",
|
||||
"unpipe": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/fresh": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
||||
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"has-proto": "^1.0.1",
|
||||
"has-symbols": "^1.0.3",
|
||||
"hasown": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.1.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-property-descriptors": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
|
||||
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz",
|
||||
"integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/http-errors": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
||||
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
|
||||
"dependencies": {
|
||||
"depd": "2.0.0",
|
||||
"inherits": "2.0.4",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": "2.0.1",
|
||||
"toidentifier": "1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"node_modules/ipaddr.js": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-descriptors": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
|
||||
},
|
||||
"node_modules/methods": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||
},
|
||||
"node_modules/negotiator": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
|
||||
"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/on-finished": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
|
||||
"dependencies": {
|
||||
"ee-first": "1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/parseurl": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-to-regexp": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
|
||||
},
|
||||
"node_modules/proxy-addr": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
|
||||
"dependencies": {
|
||||
"forwarded": "0.2.0",
|
||||
"ipaddr.js": "1.9.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
|
||||
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/raw-body": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
|
||||
"integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
"http-errors": "2.0.0",
|
||||
"iconv-lite": "0.4.24",
|
||||
"unpipe": "1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"node_modules/send": {
|
||||
"version": "0.18.0",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
|
||||
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
|
||||
"dependencies": {
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"destroy": "1.2.0",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "2.0.0",
|
||||
"mime": "1.6.0",
|
||||
"ms": "2.1.3",
|
||||
"on-finished": "2.4.1",
|
||||
"range-parser": "~1.2.1",
|
||||
"statuses": "2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/send/node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
},
|
||||
"node_modules/serve-static": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
|
||||
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
|
||||
"dependencies": {
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"parseurl": "~1.3.3",
|
||||
"send": "0.18.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/set-function-length": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz",
|
||||
"integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==",
|
||||
"dependencies": {
|
||||
"define-data-property": "^1.1.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.2.3",
|
||||
"gopd": "^1.0.1",
|
||||
"has-property-descriptors": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/setprototypeof": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz",
|
||||
"integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==",
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.6",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"object-inspect": "^1.13.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/statuses": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/toidentifier": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/type-is": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||
"dependencies": {
|
||||
"media-typer": "0.3.0",
|
||||
"mime-types": "~2.1.24"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"body-parser": "^1.20.2",
|
||||
"express": "^4.18.2"
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
## systemd (systemctl) Files
|
||||
This folder contains all the files required by systemctl on the server for updating and running the backend/frontend. These are copied onto the server and placed in the ```/etc/systemd/system``` folder.
|
||||
|
||||
## Files
|
||||
|Filename|Connected Service|
|
||||
|--------|-----------------|
|
||||
|frontend-next.service|Folder: frontend-next|
|
||||
|server_update_hook.service|Folder: backend/server_update_hook|
|
||||
@@ -1,19 +0,0 @@
|
||||
[Unit]
|
||||
Description=ChatMaps Frontend Server
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
Group=root
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
WorkingDirectory=/root/ChatMaps/frontend-next
|
||||
ExecStartPre=/usr/bin/git pull
|
||||
ExecStartPre=/usr/bin/git checkout main
|
||||
ExecStartPre=/usr/bin/npm install
|
||||
ExecStartPre=/usr/bin/npm run build
|
||||
ExecStart=/usr/bin/npm run start
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -1,18 +0,0 @@
|
||||
[Unit]
|
||||
Description=ChatMaps Backend Server Updater / Restarter
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
Group=root
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
WorkingDirectory=/root/ChatMaps/backend/server_update_hook
|
||||
ExecStartPre=/usr/bin/git pull
|
||||
ExecStartPre=/usr/bin/git checkout main
|
||||
ExecStartPre=/usr/bin/npm install
|
||||
ExecStart=/usr/bin/node api.js
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
After Width: | Height: | Size: 5.5 MiB |
|
After Width: | Height: | Size: 4.5 MiB |
|
After Width: | Height: | Size: 4.7 MiB |
@@ -1,3 +1,9 @@
|
||||
{
|
||||
"extends": "next/core-web-vitals"
|
||||
"extends": ["next/babel","next/core-web-vitals" ],
|
||||
"rules": {
|
||||
"no-unused-vars": ["warn", { "vars": "all", "args": "after-used", "ignoreRestSiblings": false }],
|
||||
"jsx-a11y/alt-text": "off",
|
||||
"@next/next/no-img-element": "off",
|
||||
"no-console": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { CapacitorConfig } from '@capacitor/cli';
|
||||
|
||||
const config: CapacitorConfig = {
|
||||
appId: 'com.jacsn.chatmaps',
|
||||
appName: 'ChatMaps',
|
||||
webDir: 'out',
|
||||
server: {
|
||||
androidScheme: 'https'
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -0,0 +1,21 @@
|
||||
import { initializeApp, getApps, getApp } from "firebase/app";
|
||||
import { getAuth } from "firebase/auth";
|
||||
import { getDatabase} from "firebase/database"
|
||||
import {getStorage} from "firebase/storage"
|
||||
|
||||
var config = {
|
||||
apiKey: "AIzaSyDbDPjQGt-lIjNPeTG-Q5AECM1m0vtOr2c",
|
||||
authDomain: "chatmaps-3e7fa.firebaseapp.com",
|
||||
projectId: "chatmaps-3e7fa",
|
||||
storageBucket: "chatmaps-3e7fa.appspot.com",
|
||||
messagingSenderId: "771010649524",
|
||||
appId: "1:771010649524:web:b6e66d3457820c817b26e1",
|
||||
databaseURL: "https://chatmaps-3e7fa-default-rtdb.firebaseio.com/",
|
||||
}
|
||||
|
||||
var app = getApps().length > 0 ? getApp() : initializeApp(config);
|
||||
var auth = getAuth(app);
|
||||
var storage = getStorage(app);
|
||||
var database = getDatabase(app);
|
||||
|
||||
export { auth, app, database, storage };
|
||||
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 9.5 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
@@ -1,4 +1,4 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {};
|
||||
const nextConfig = {output: 'export'};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
@@ -5,15 +5,30 @@
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"start": "npx serve@latest out",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@capacitor/android": "^5.7.4",
|
||||
"@capacitor/core": "^5.7.4",
|
||||
"@capacitor/geolocation": "^5.0.7",
|
||||
"@emotion/react": "^11.11.4",
|
||||
"@emotion/styled": "^11.11.5",
|
||||
"@headlessui/react": "^1.7.18",
|
||||
"@mui/icons-material": "^5.15.14",
|
||||
"@mui/material": "^5.15.14",
|
||||
"firebase": "^10.6.0",
|
||||
"next": "^14.1.0",
|
||||
"pigeon-maps": "^0.21.3",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"react-beforeunload": "^2.6.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-firebase-hooks": "^5.1.1",
|
||||
"react-hook-form": "^7.50.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@capacitor/assets": "^3.0.5",
|
||||
"@capacitor/cli": "^5.7.4",
|
||||
"autoprefixer": "^10.0.1",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.1.0",
|
||||
|
||||
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 964 B |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 26 KiB |
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"icons": [
|
||||
{
|
||||
"src": "../icons/icon-48.webp",
|
||||
"type": "image/png",
|
||||
"sizes": "48x48",
|
||||
"purpose": "any maskable"
|
||||
},
|
||||
{
|
||||
"src": "../icons/icon-72.webp",
|
||||
"type": "image/png",
|
||||
"sizes": "72x72",
|
||||
"purpose": "any maskable"
|
||||
},
|
||||
{
|
||||
"src": "../icons/icon-96.webp",
|
||||
"type": "image/png",
|
||||
"sizes": "96x96",
|
||||
"purpose": "any maskable"
|
||||
},
|
||||
{
|
||||
"src": "../icons/icon-128.webp",
|
||||
"type": "image/png",
|
||||
"sizes": "128x128",
|
||||
"purpose": "any maskable"
|
||||
},
|
||||
{
|
||||
"src": "../icons/icon-192.webp",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192",
|
||||
"purpose": "any maskable"
|
||||
},
|
||||
{
|
||||
"src": "../icons/icon-256.webp",
|
||||
"type": "image/png",
|
||||
"sizes": "256x256",
|
||||
"purpose": "any maskable"
|
||||
},
|
||||
{
|
||||
"src": "../icons/icon-512.webp",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512",
|
||||
"purpose": "any maskable"
|
||||
}
|
||||
],
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>
|
||||
|
Before Width: | Height: | Size: 629 B |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 173 KiB |
|
After Width: | Height: | Size: 173 KiB |
@@ -0,0 +1,17 @@
|
||||
import { Inter } from "next/font/google";
|
||||
import "../globals.css";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
export const metadata = {
|
||||
title: "ChatMaps: Home",
|
||||
description: "ChatMaps: Social Media for College Students",
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
"use client";
|
||||
// System Imports
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
// Dependencies
|
||||
import Drawer from '@mui/material/Drawer';
|
||||
|
||||
// Firebase Imports
|
||||
import { auth, database } from "../../../firebase-config";
|
||||
import { ref, onValue } from "firebase/database";
|
||||
import { useAuthState } from "react-firebase-hooks/auth"
|
||||
|
||||
// Component Imports
|
||||
import { Header } from "../../components/app/header";
|
||||
import { HomePage } from "../../components/app/page/home";
|
||||
import { Sidebar } from "../../components/app/sidebar/home";
|
||||
import {useWindowSize} from "../../components/app/datatypes";
|
||||
|
||||
// Capacitor Import
|
||||
import { Geolocation } from '@capacitor/geolocation';
|
||||
|
||||
/**
|
||||
* Contains most everything for the app homepage
|
||||
* @returns {Object} Home Page
|
||||
*/
|
||||
function Home() {
|
||||
// State variables for app page
|
||||
const [user, setUser] = useState(null); // user data
|
||||
const [loadingLoc, setLoadingLoc] = useState(true); // location variable loading, true = loading, false = finished loading
|
||||
const [authUser] = useAuthState(auth) // auth user object (used to obtain other user object)
|
||||
const [drawerOpen, setDrawerOpen] = useState(true); // drawer open state
|
||||
const [coords, setCoords] = useState(null)
|
||||
|
||||
var windowSize = useWindowSize()
|
||||
useEffect(() => {
|
||||
if (windowSize.width < 767) {
|
||||
setDrawerOpen(false)
|
||||
} else {
|
||||
setDrawerOpen(true)
|
||||
}
|
||||
}, [windowSize])
|
||||
|
||||
// Authentication Verification / Redirection if Profile Data not Filled out
|
||||
useEffect(() => {
|
||||
if (authUser) {
|
||||
onValue(ref(database, `users/${authUser.uid}`), (userData) => {
|
||||
userData = userData.val();
|
||||
if (userData) {
|
||||
setUser(userData);
|
||||
} else {
|
||||
window.location.href = "/onboarding";
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [authUser])
|
||||
|
||||
useEffect(() => {
|
||||
Geolocation.getCurrentPosition().then((position) => {
|
||||
setCoords(position.coords);
|
||||
setLoadingLoc(false);
|
||||
});
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="overflow-hidden h-dvh">
|
||||
{user && (
|
||||
<div className="overflow-hidden h-dvh">
|
||||
{/* Left Side of Page */}
|
||||
<div className="overflow-hidden h-dvh md:mr-[405px]">
|
||||
{/* Header */}
|
||||
<Header
|
||||
mainTab={"home"}
|
||||
user={user}
|
||||
sidebarControl={() => {setDrawerOpen(!drawerOpen)}}
|
||||
/>
|
||||
{/* Main Page Section */}
|
||||
<div className="mr-2 h-[calc(100%-110px)]">
|
||||
{!loadingLoc && (
|
||||
<HomePage loc={coords} user={user} />
|
||||
)}
|
||||
{loadingLoc && (
|
||||
<HomePage loc={null} user={user} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* Sidebar (Right Side of Page) */}
|
||||
<Drawer open={drawerOpen} anchor={"right"} variant={windowSize.width > 767? "persistent": "temporary"} onClose={() => {setDrawerOpen(false)}} sx={{
|
||||
width: windowSize.width > 767? 400: "80%",
|
||||
marginTop: 10,
|
||||
flexShrink: 0,
|
||||
'& .MuiDrawer-paper': {
|
||||
width: windowSize.width > 767? 400: "80%",
|
||||
borderLeft: 0,
|
||||
},
|
||||
}}>
|
||||
<div className="shadow-2xl">
|
||||
<Sidebar user={user} location={coords} loadingLoc={loadingLoc}/>
|
||||
</div>
|
||||
</Drawer>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Home;
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Inter } from "next/font/google";
|
||||
import "../globals.css";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
export const metadata = {
|
||||
title: "ChatMaps: Chat",
|
||||
description: "ChatMaps: Social Media for College Students",
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
"use client";
|
||||
// System Imports
|
||||
import Drawer from '@mui/material/Drawer';
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
// Firebase Imports
|
||||
import { auth, database } from "../../../firebase-config";
|
||||
import { ref, onValue, set, onDisconnect } from "firebase/database";
|
||||
import { useAuthState } from "react-firebase-hooks/auth"
|
||||
|
||||
// Component Imports
|
||||
import { Header } from "../../components/app/header";
|
||||
import { ChatRoom } from "../../components/app/page/chat";
|
||||
import { Sidebar } from "../../components/app/sidebar/chat";
|
||||
import {useWindowSize} from "../../components/app/datatypes";
|
||||
|
||||
|
||||
/**
|
||||
* Chat Page
|
||||
* @returns {Object} Chat Page
|
||||
*/
|
||||
function Chat() {
|
||||
// State variables for chat page
|
||||
const [user, setUser] = useState(null); // user data
|
||||
const [chatRoomObj, setChatRoomObj] = useState(null); // Current chatroom object
|
||||
const [doneLoading, setDoneLoading] = useState(false) // is the page done loading or not
|
||||
const [authUser] = useAuthState(auth) // auth user object (used to obtain other user object)
|
||||
const [drawerOpen, setDrawerOpen] = useState(true); // drawer open state
|
||||
|
||||
var windowSize = useWindowSize()
|
||||
useEffect(() => {
|
||||
if (windowSize.width < 767) {
|
||||
setDrawerOpen(false)
|
||||
} else {
|
||||
setDrawerOpen(true)
|
||||
}
|
||||
}, [windowSize])
|
||||
|
||||
// Authentication Verification / Redirection if Profile Data not Filled out
|
||||
useEffect(() => {
|
||||
if (authUser) {
|
||||
onValue(ref(database, `users/${authUser.uid}`), (userData) => {
|
||||
userData = userData.val();
|
||||
if (userData) {
|
||||
setUser(userData);
|
||||
} else {
|
||||
window.location.href = "/onboarding";
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [authUser])
|
||||
|
||||
// Users URL params to load proper chatroom, then logs the user into that room
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
const searchParams = new URLSearchParams(document.location.search);
|
||||
var path = searchParams.get("room")
|
||||
|
||||
/*// Send entered message
|
||||
var payload = {
|
||||
body: "entered",
|
||||
user: user.username,
|
||||
isSystem: true,
|
||||
timestamp: new Date().getTime(),
|
||||
uid: user.uid,
|
||||
};
|
||||
set(
|
||||
ref(
|
||||
database,
|
||||
`/rooms/${path}/chats/${new Date().getTime()}-${user.username}`
|
||||
),
|
||||
payload
|
||||
);*/
|
||||
|
||||
// Add user to online for room
|
||||
set(ref(database, `/rooms/${path}/users/online/${user.uid}`), user)
|
||||
|
||||
// Removes user from room on disconnect (reload, window close, internet lost)
|
||||
onDisconnect(ref(database, `/rooms/${path}/users/online/${user.uid}`)).remove()
|
||||
|
||||
// Sends leaving message on disconnect (Timestamp function used due to new onDisconnect stuff)
|
||||
/*someRef = ref(database, `/rooms/${path}/chats/${new Date().getTime()}-${user.username}`)
|
||||
onDisconnect(someRef).set({
|
||||
body: "left",
|
||||
user: user.username,
|
||||
isSystem: true,
|
||||
timestamp: serverTimestamp(),
|
||||
uid: user.uid,
|
||||
})*/
|
||||
|
||||
onValue(ref(database, `/rooms/${path}`), (roomData) => {
|
||||
roomData = roomData.val();
|
||||
setChatRoomObj(roomData)
|
||||
if (!doneLoading) {
|
||||
setDoneLoading(true)
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{(authUser && doneLoading) && (
|
||||
<div className="overflow-hidden h-dvh">
|
||||
{/* Left Side of Page */}
|
||||
<div className="overflow-hidden h-dvh md:mr-[400px]">
|
||||
{/* Header */}
|
||||
<Header
|
||||
mainTab={"chat"}
|
||||
chatRoomObj={chatRoomObj}
|
||||
user={user}
|
||||
sidebarControl={() => {setDrawerOpen(!drawerOpen)}}
|
||||
/>
|
||||
{/* Main Page Section */}
|
||||
<div className="mr-2 h-[calc(100%-110px)]">
|
||||
<ChatRoom roomObj={chatRoomObj} user={user} />
|
||||
</div>
|
||||
</div>
|
||||
{/* Sidebar (Right Side of Page) */}
|
||||
<Drawer open={drawerOpen} anchor={"right"} variant={windowSize.width > 767? "persistent": "temporary"} onClose={() => {setDrawerOpen(false)}} sx={{
|
||||
width: windowSize.width > 767? 400: "80%",
|
||||
marginTop: 10,
|
||||
flexShrink: 0,
|
||||
'& .MuiDrawer-paper': {
|
||||
width: windowSize.width > 767? 400: "80%",
|
||||
borderLeft: 0,
|
||||
},
|
||||
}}>
|
||||
<div className="shadow-2xl">
|
||||
<Sidebar chatRoomObj={chatRoomObj}/>
|
||||
</div>
|
||||
</Drawer>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Chat;
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Inter } from "next/font/google";
|
||||
import "../globals.css";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
export const metadata = {
|
||||
title: "ChatMaps: DM",
|
||||
description: "ChatMaps: Social Media for College Students",
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
"use client";
|
||||
// System Imports
|
||||
import Drawer from '@mui/material/Drawer';
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
// Firebase Imports
|
||||
import { auth, database } from "../../../firebase-config";
|
||||
import { ref, onValue, set, onDisconnect } from "firebase/database";
|
||||
import { useAuthState } from "react-firebase-hooks/auth"
|
||||
|
||||
// Component Imports
|
||||
import { Header } from "../../components/app/header";
|
||||
import { DMRoom } from "../../components/app/friends/page";
|
||||
import { Sidebar } from "../../components/app/sidebar/dm";
|
||||
import {useWindowSize} from "../../components/app/datatypes";
|
||||
|
||||
|
||||
/**
|
||||
* DM Page
|
||||
* @returns {Object} Chat Page
|
||||
*/
|
||||
function Chat() {
|
||||
// State variables for chat page
|
||||
const [user, setUser] = useState(null); // user data
|
||||
const [chatRoomObj, setChatRoomObj] = useState(null); // Current chatroom object
|
||||
const [doneLoading, setDoneLoading] = useState(false) // is the page done loading or not
|
||||
const [authUser] = useAuthState(auth) // auth user object (used to obtain other user object)
|
||||
const [drawerOpen, setDrawerOpen] = useState(true); // drawer open state
|
||||
|
||||
var windowSize = useWindowSize()
|
||||
useEffect(() => {
|
||||
if (windowSize.width < 767) {
|
||||
setDrawerOpen(false)
|
||||
} else {
|
||||
setDrawerOpen(true)
|
||||
}
|
||||
}, [windowSize])
|
||||
|
||||
// Authentication Verification / Redirection if Profile Data not Filled out
|
||||
useEffect(() => {
|
||||
if (authUser) {
|
||||
onValue(ref(database, `users/${authUser.uid}`), (userData) => {
|
||||
userData = userData.val();
|
||||
if (userData) {
|
||||
setUser(userData);
|
||||
} else {
|
||||
window.location.href = "/onboarding";
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [authUser])
|
||||
|
||||
// Users URL params to load proper chatroom, then logs the user into that room
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
const searchParams = new URLSearchParams(document.location.search);
|
||||
var path = searchParams.get("dm")
|
||||
|
||||
/*// Send entered message
|
||||
var payload = {
|
||||
body: "entered",
|
||||
user: user.username,
|
||||
isSystem: true,
|
||||
timestamp: new Date().getTime(),
|
||||
uid: user.uid,
|
||||
};
|
||||
set(
|
||||
ref(
|
||||
database,
|
||||
`/rooms/${path}/chats/${new Date().getTime()}-${user.username}`
|
||||
),
|
||||
payload
|
||||
);*/
|
||||
|
||||
// Add user to online for room
|
||||
set(ref(database, `/dms/${path}/users/online/${user.uid}`), user)
|
||||
|
||||
// Removes user from room on disconnect (reload, window close, internet lost)
|
||||
onDisconnect(ref(database, `/dms/${path}/users/online/${user.uid}`)).remove()
|
||||
|
||||
// Sends leaving message on disconnect (Timestamp function used due to new onDisconnect stuff)
|
||||
/*someRef = ref(database, `/rooms/${path}/chats/${new Date().getTime()}-${user.username}`)
|
||||
onDisconnect(someRef).set({
|
||||
body: "left",
|
||||
user: user.username,
|
||||
isSystem: true,
|
||||
timestamp: serverTimestamp(),
|
||||
uid: user.uid,
|
||||
})*/
|
||||
|
||||
onValue(ref(database, `/dms/${path}`), (roomData) => {
|
||||
roomData = roomData.val();
|
||||
setChatRoomObj(roomData)
|
||||
if (!doneLoading) {
|
||||
setDoneLoading(true)
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{(authUser && doneLoading) && (
|
||||
<div className="overflow-hidden h-dvh">
|
||||
{/* Left Side of Page */}
|
||||
<div className="overflow-hidden h-dvh md:mr-[400px]">
|
||||
{/* Header */}
|
||||
<Header
|
||||
mainTab={"dm"}
|
||||
chatRoomObj={chatRoomObj}
|
||||
user={user}
|
||||
sidebarControl={() => {setDrawerOpen(!drawerOpen)}}
|
||||
/>
|
||||
{/* Main Page Section */}
|
||||
<div className="mr-2 h-[calc(100%-110px)]">
|
||||
<DMRoom roomObj={chatRoomObj} user={user} />
|
||||
</div>
|
||||
</div>
|
||||
{/* Sidebar (Right Side of Page) */}
|
||||
<Drawer open={drawerOpen} anchor={"right"} variant={windowSize.width > 767? "persistent": "temporary"} onClose={() => {setDrawerOpen(false)}} sx={{
|
||||
width: windowSize.width > 767? 400: "80%",
|
||||
marginTop: 10,
|
||||
flexShrink: 0,
|
||||
'& .MuiDrawer-paper': {
|
||||
width: windowSize.width > 767? 400: "80%",
|
||||
borderLeft: 0,
|
||||
},
|
||||
}}>
|
||||
<div className="shadow-2xl">
|
||||
<Sidebar chatRoomObj={chatRoomObj} user={user}/>
|
||||
</div>
|
||||
</Drawer>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Chat;
|
||||
|
Before Width: | Height: | Size: 25 KiB |
@@ -2,17 +2,20 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
main {
|
||||
body {
|
||||
background-color: aliceblue;
|
||||
text-align: center;
|
||||
text-wrap:pretty;
|
||||
text-wrap: pretty;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: rgb(205, 205, 205);
|
||||
background: #dee0e0;
|
||||
border-color: black;
|
||||
border: 5px;
|
||||
border-radius: 5px;
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
filter: drop-shadow(0 25px 25px rgb(0 0 0 / 0.15));
|
||||
}
|
||||
|
||||
button:hover {
|
||||
@@ -23,5 +26,12 @@ input {
|
||||
border: 1px solid black;
|
||||
border-radius: 4px;
|
||||
padding: 10px 10px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
input.err {
|
||||
border: 2px solid red;
|
||||
border-radius: 4px;
|
||||
padding: 10px 10px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Inter } from "next/font/google";
|
||||
import "../globals.css";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
export const metadata = {
|
||||
title: "ChatMaps: Login",
|
||||
description: "ChatMaps: Social Media for College Students",
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
"use client";
|
||||
// System Imports
|
||||
import "../globals.css";
|
||||
import { useForm, Form } from "react-hook-form";
|
||||
import Link from "next/link"
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
// Firebase Imports
|
||||
import { auth } from "../../../firebase-config";
|
||||
import {setPersistence,signInWithEmailAndPassword,indexedDBLocalPersistence } from "firebase/auth";
|
||||
|
||||
/**
|
||||
* Login Page
|
||||
* @returns {Object} Login Page
|
||||
*/
|
||||
function Login() {
|
||||
var router = useRouter();
|
||||
var {register,control,setError,reset,formState: { errors, isSubmitting, isSubmitted },} = useForm();
|
||||
|
||||
/**
|
||||
* Logs into Firebase authentication
|
||||
* @param {JSON} data - User Login Data (data.email, data.password)
|
||||
* @returns {void}
|
||||
*/
|
||||
function authenticate({data}) {
|
||||
setPersistence(auth, indexedDBLocalPersistence).then(() => {
|
||||
signInWithEmailAndPassword(auth, data.email, data.password).then(
|
||||
(userCredential) => {
|
||||
if (userCredential.user) {
|
||||
router.push("/app");
|
||||
}
|
||||
}
|
||||
).catch((error) => {
|
||||
if (error = "auth/invalid-credential") {
|
||||
const formError = {
|
||||
type: "server",
|
||||
message: "Username or Password Incorrect",
|
||||
};
|
||||
// set same error in both:
|
||||
setError("password", formError);
|
||||
setError("email", formError);
|
||||
reset(data,{keepErrors: true})
|
||||
} else {
|
||||
const formError = {
|
||||
type: "server",
|
||||
message: "Server Error, Please try again later.",
|
||||
};
|
||||
// set same error in both:
|
||||
setError("password", formError);
|
||||
setError("email", formError);
|
||||
reset(data,{keepErrors: true})
|
||||
}
|
||||
// ..
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="grid h-screen place-items-center">
|
||||
<div>
|
||||
<Link href="/">
|
||||
<img src="logos/logo_transparent_inverse.png" />
|
||||
</Link>
|
||||
<span className="text-[36px]">Chat with friends!</span>
|
||||
<div>
|
||||
<h3 className="text-[24px] mt-[25px] mb-2">Login</h3>
|
||||
{errors.email && errors.password && (
|
||||
<div className="text-[red] mb-2 text-[18px] text-bold">
|
||||
Invalid Email or Password.
|
||||
</div>
|
||||
)}
|
||||
<Form
|
||||
onSubmit={authenticate}
|
||||
encType={"application/json"}
|
||||
control={control}
|
||||
>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
className={errors.email && errors.password && "err"}
|
||||
{...register("email", { required: true })}
|
||||
placeholder="Enter Email Address"
|
||||
/>
|
||||
<br />
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
className={errors.email && errors.password && "err"}
|
||||
{...register("password", { required: true })}
|
||||
placeholder="Enter Password"
|
||||
/>
|
||||
<br />
|
||||
<button
|
||||
type="submit"
|
||||
className="inline-flex items-center transition ease-in-out duration-150 m-5 bg-cyan-500 text-white font-bold py-2 px-4 rounded-full"
|
||||
>
|
||||
{(isSubmitting || isSubmitted) && (
|
||||
<span className="inline-block">
|
||||
<svg
|
||||
class="animate-spin -ml-1 mr-3 h-5 w-5 text-white"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
strokeWidth="4"
|
||||
></circle>
|
||||
<path
|
||||
class="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
</span>
|
||||
)}
|
||||
Log In
|
||||
</button>
|
||||
<br />
|
||||
Need an account? <Link href="/register">Sign Up</Link>
|
||||
<br />
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Login;
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Inter } from "next/font/google";
|
||||
import "../globals.css";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
export const metadata = {
|
||||
title: "ChatMaps: Onboarding",
|
||||
description: "ChatMaps: Social Media for College Students",
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
"use client";
|
||||
// System Imports
|
||||
import "../globals.css";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
// Firebase Imports
|
||||
import { ref, set } from "firebase/database";
|
||||
import { auth, database } from "../../../firebase-config";
|
||||
import { onAuthStateChanged } from "firebase/auth";
|
||||
|
||||
/**
|
||||
* Creates user data in Firebase DB
|
||||
* @param {JSON} data - User data to be stored in Firebase DB ( from form )
|
||||
* @return {Boolean} - True if user data is stored, False if user data is not stored
|
||||
*/
|
||||
function createUser(data) {
|
||||
onAuthStateChanged(auth, (user) => {
|
||||
if (user.uid) {
|
||||
data.uid = user.uid;
|
||||
data.defined = true;
|
||||
data.email = user.email;
|
||||
set(ref(database, `users/${user.uid}`), data);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Onboarding Page
|
||||
* @returns {Object} - Onboarding Page
|
||||
*/
|
||||
function Onboarding() {
|
||||
var router = useRouter();
|
||||
var { register, handleSubmit } = useForm();
|
||||
|
||||
function Onboard(data) {
|
||||
createUser(data);
|
||||
router.push("/app");
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<div className="grid h-screen place-items-center">
|
||||
<div>
|
||||
<img src="logos/logo_transparent_inverse.png" />
|
||||
<span className="text-[36px]">Chat with friends!</span>
|
||||
<div className="m-5">
|
||||
Welcome to ChatMaps! We are excited to have you join our community!
|
||||
<br />
|
||||
First we just need a little bit of information from you to get
|
||||
started.
|
||||
</div>
|
||||
<form action="#" onSubmit={handleSubmit(Onboard)}>
|
||||
<input
|
||||
type="text"
|
||||
{...register("username")}
|
||||
placeholder="Display Name"
|
||||
/>
|
||||
<br />
|
||||
<input
|
||||
type="text"
|
||||
{...register("firstName")}
|
||||
placeholder="First Name"
|
||||
/>
|
||||
<br />
|
||||
<input
|
||||
type="text"
|
||||
{...register("lastName")}
|
||||
placeholder="Last Name"
|
||||
/>
|
||||
<br />
|
||||
<button
|
||||
type="submit"
|
||||
className="inline-flex items-center transition ease-in-out duration-150 m-5 bg-cyan-500 text-white font-bold py-2 px-4 rounded-full"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Onboarding;
|
||||
@@ -1,27 +1,111 @@
|
||||
export default function Home() {
|
||||
"use client";
|
||||
import { useState, useEffect } from "react";
|
||||
import Link from "next/link"
|
||||
import { auth, database } from "../../firebase-config";
|
||||
import { ref, get } from "firebase/database";
|
||||
import { onAuthStateChanged } from "firebase/auth";
|
||||
|
||||
// Capacitor Import
|
||||
import { Geolocation } from '@capacitor/geolocation';
|
||||
|
||||
/**
|
||||
* Home Page
|
||||
* @returns {Object} - Home Page
|
||||
*/
|
||||
function Home() {
|
||||
const [isLoadingLoc, setLoadingLoc] = useState(true); // is location loading?
|
||||
const [roomCount, setRoomCount] = useState(null); // local room count
|
||||
const [isAuthenticated, setAuth] = useState(false); // is user authenticated?
|
||||
const [coords, setCoords] = useState(null)
|
||||
|
||||
// Authentication
|
||||
useEffect(() => {
|
||||
onAuthStateChanged(auth, async (user) => {
|
||||
if (user) {
|
||||
setAuth(true);
|
||||
} else {
|
||||
setAuth(false);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
Geolocation.getCurrentPosition().then((position) => {
|
||||
setCoords(position.coords);
|
||||
setLoadingLoc(false);
|
||||
});
|
||||
}, [])
|
||||
|
||||
// Update room count on location fix
|
||||
useEffect(() => {
|
||||
if (coords) {
|
||||
var path =
|
||||
String(coords.latitude.toFixed(2)).replace(".", "") +
|
||||
"/" +
|
||||
String(coords.longitude.toFixed(2)).replace(".", "");
|
||||
get(ref(database, `/rooms/${path}`)).then((snapshot) => {
|
||||
if (snapshot.exists()) {
|
||||
var count = 0;
|
||||
for (var room in snapshot.val()) {
|
||||
count += 1;
|
||||
}
|
||||
setRoomCount(count);
|
||||
} else {
|
||||
setRoomCount(0);
|
||||
}
|
||||
setLoadingLoc(false);
|
||||
});
|
||||
}
|
||||
}, [coords])
|
||||
|
||||
return (
|
||||
<main className="flex min-h-screen flex-col justify-between p-24">
|
||||
<h1>Welcome to ChatMaps.</h1>
|
||||
|
||||
|
||||
<div id="room">
|
||||
<label>Room </label>
|
||||
<select>
|
||||
<option>Room 1</option>
|
||||
<option>Room 2</option>
|
||||
<option>Room 3</option>
|
||||
</select>
|
||||
<button>Join Room</button>
|
||||
<div>
|
||||
<div className="grid h-screen place-items-center">
|
||||
<div>
|
||||
<img src="logos/logo_transparent_inverse.png" />
|
||||
<span className="text-[36px]">Chat with friends!</span>
|
||||
<div className="m-5">
|
||||
{!isAuthenticated && (
|
||||
<div>
|
||||
<Link href="/login">
|
||||
<button className="bg-cyan-500 text-white font-bold py-2 px-4 rounded-full">
|
||||
Login
|
||||
</button>
|
||||
</Link>
|
||||
<Link href="/register">
|
||||
<button className="bg-cyan-500 text-white font-bold py-2 px-4 rounded-full">
|
||||
Register
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
{isAuthenticated && (
|
||||
<Link href="/app">
|
||||
<button className="bg-cyan-500 text-white font-bold py-2 px-4 rounded-full">
|
||||
Continue to App
|
||||
</button>
|
||||
</Link>
|
||||
)}
|
||||
{!isLoadingLoc && roomCount == 1 && (
|
||||
<div className="text-[24px] pt-10">
|
||||
Join others in {roomCount} room near you!
|
||||
</div>
|
||||
)}
|
||||
{!isLoadingLoc && roomCount != 1 && roomCount != 0 && (
|
||||
<div className="text-[24px] pt-10">
|
||||
Join others in {roomCount} rooms near you!
|
||||
</div>
|
||||
)}
|
||||
{(isLoadingLoc || (roomCount == 0 && !isLoadingLoc)) && (
|
||||
<div className="text-[24px] pt-10">
|
||||
Start the conversation today!
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="message">
|
||||
<label>Enter a message</label>
|
||||
<input />
|
||||
<button>Send</button>
|
||||
</div>
|
||||
|
||||
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Home;
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Inter } from "next/font/google";
|
||||
import "../globals.css";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
export const metadata = {
|
||||
title: "ChatMaps: Register",
|
||||
description: "ChatMaps: Social Media for College Students",
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
"use client";
|
||||
// System Imports
|
||||
import "../globals.css";
|
||||
import { useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useForm, Form } from "react-hook-form";
|
||||
import Link from "next/link"
|
||||
|
||||
// Firebase Imports
|
||||
import {createUserWithEmailAndPassword,signInWithEmailAndPassword,setPersistence,indexedDBLocalPersistence,} from "firebase/auth";
|
||||
import { auth } from "../../../firebase-config";
|
||||
|
||||
/**
|
||||
* Signs up user in Firebase Authentication
|
||||
* @param {JSON} data - User signup data (data.email, data.password)
|
||||
* @returns {Boolean} - True if user is signed up, False if user is not signed up
|
||||
* @async
|
||||
*/
|
||||
async function Signup(data) {
|
||||
var userCredential = await createUserWithEmailAndPassword(
|
||||
auth,
|
||||
data.email,
|
||||
data.password
|
||||
);
|
||||
if (userCredential.user) {
|
||||
setPersistence(auth, indexedDBLocalPersistence).then(() => {
|
||||
signInWithEmailAndPassword(auth, data.email, data.password).then(
|
||||
() => {
|
||||
return true;
|
||||
}
|
||||
);
|
||||
});
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Register Page
|
||||
* @returns {Object} - Registration Page
|
||||
*/
|
||||
function Register() {
|
||||
var router = useRouter();
|
||||
var {
|
||||
register,
|
||||
control,
|
||||
formState: { errors },
|
||||
} = useForm();
|
||||
var emailRegex =
|
||||
/(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/;
|
||||
var [passwordMismatch, setPasswordMismatch] = useState(false);
|
||||
var [passwordLength, setPasswordLength] = useState(false);
|
||||
const passwordMatch = (data) => {
|
||||
return data.password === data.passwordCheck;
|
||||
};
|
||||
|
||||
const passwordLengthMatch = (data) => {
|
||||
return data.password.length >= 6;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form onSubmit Handler
|
||||
* @params {JSON} data - Form data
|
||||
*/
|
||||
function onSubmit({ data }) {
|
||||
if (passwordMatch(data) && passwordLengthMatch(data)) {
|
||||
setPasswordMismatch(false);
|
||||
setPasswordLength(false)
|
||||
if (Signup(data)) {
|
||||
router.push("/onboarding");
|
||||
}
|
||||
} else {
|
||||
if (!passwordMatch(data)) {
|
||||
setPasswordMismatch(true);
|
||||
}
|
||||
if (!passwordLengthMatch(data)) {
|
||||
setPasswordLength(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="grid h-screen place-items-center">
|
||||
<div>
|
||||
<Link href="/">
|
||||
<img src="logos/logo_transparent_inverse.png" />
|
||||
</Link>
|
||||
<span className="text-[36px]">Chat with friends!</span>
|
||||
<div>
|
||||
<h3 className="text-[24px] mt-[15px]">Register</h3>
|
||||
<Form
|
||||
onSubmit={onSubmit}
|
||||
encType={"application/json"}
|
||||
control={control}
|
||||
>
|
||||
<input
|
||||
type="email"
|
||||
{...register("email", { required: true, pattern: emailRegex })}
|
||||
className={errors.email && "err"}
|
||||
placeholder="Enter Email Address"
|
||||
/>
|
||||
<br />
|
||||
<input
|
||||
type="password"
|
||||
{...register("password", { required: true })}
|
||||
className={
|
||||
errors.password && errors.password.type == "required" && "err"
|
||||
}
|
||||
placeholder="Enter Password"
|
||||
/>
|
||||
<br />
|
||||
<input
|
||||
type="password"
|
||||
{...register("passwordCheck", { required: false })}
|
||||
className={
|
||||
errors.passwordCheck &&
|
||||
errors.passwordCheck.type == "required" &&
|
||||
"err"
|
||||
}
|
||||
placeholder="Re-enter Password"
|
||||
/>
|
||||
<br />
|
||||
{passwordMismatch && (
|
||||
<p className="text-red-500">Passwords do not match</p>
|
||||
)}
|
||||
{passwordLength && (
|
||||
<p className="text-red-500">Password must be at least 6 characters</p>
|
||||
)}
|
||||
<button
|
||||
type="submit"
|
||||
className=" m-5 bg-cyan-500 text-white font-bold py-2 px-4 rounded-full"
|
||||
>
|
||||
{" "}
|
||||
Register
|
||||
</button>
|
||||
<br />
|
||||
Have an account? <Link href="/login">Login</Link>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Register;
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Inter } from "next/font/google";
|
||||
import "../globals.css";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
export const metadata = {
|
||||
title: "ChatMaps: User Profile",
|
||||
description: "ChatMaps: Social Media for College Students",
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
"use client";
|
||||
// System Imports
|
||||
import { useState, useEffect } from "react";
|
||||
import { auth, database } from "../../../firebase-config";
|
||||
import { ref, onValue, get } from "firebase/database";
|
||||
import { onAuthStateChanged } from "firebase/auth";
|
||||
|
||||
// Refactored Component Imports
|
||||
|
||||
// Data Structure Imports
|
||||
import { ProfileRoom } from "../../components/app/profile/ProfileRoom";
|
||||
import { ProfileEdit } from "../../components/app/profile/ProfileEdit";
|
||||
import { Interest } from "../../components/app/profile/Interest";
|
||||
|
||||
// Header Import
|
||||
import { Header } from "../../components/app/header";
|
||||
|
||||
// Friend Import
|
||||
import { addFriend } from "../../components/app/friends/friends";
|
||||
|
||||
/**
|
||||
* User Profile Page
|
||||
* @returns {Object} - User Profile Page
|
||||
*/
|
||||
function UserProfile() {
|
||||
const [profileData, setProfileData] = useState(null); // Profile Data
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false); // Determines if user is authenticated
|
||||
const [user, setUser] = useState(null); // User Data
|
||||
const [userInterestArray, setUserInterestArray] = useState(null); // Array of user's interests
|
||||
const [userRoomsArray, setUserRoomsArray] = useState(null); // Array of user's rooms
|
||||
const [isOwner, setIsOwner] = useState(false); // Determines if user is owner of profile
|
||||
const [friends, setFriends] = useState(false); // is user a friend?
|
||||
const [isPending, setPending] = useState(false); // is friend request pending?
|
||||
|
||||
// Handles Edit State in Component, shares useState with ProfileEdit
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const handleIsEditing = (newValue) => {
|
||||
setIsEditing(newValue);
|
||||
};
|
||||
|
||||
// Authentication
|
||||
useEffect(() => {
|
||||
onAuthStateChanged(auth, (user) => {
|
||||
const searchParams = new URLSearchParams(document.location.search);
|
||||
var userUID = searchParams.get("uid")
|
||||
if (user) {
|
||||
get(ref(database, `users/${user.uid}`)).then((userData) => {
|
||||
userData = userData.val();
|
||||
if (userData) {
|
||||
if (userData.uid == userUID) {
|
||||
setIsOwner(true);
|
||||
}
|
||||
setUser(userData);
|
||||
setIsAuthenticated(true);
|
||||
} else {
|
||||
window.location.href = "/onboarding";
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Grabs profile user data
|
||||
useEffect(() => {
|
||||
const searchParams = new URLSearchParams(document.location.search);
|
||||
var userUID = searchParams.get("uid")
|
||||
onValue(ref(database, "/users/" + userUID), (snapshot) => {
|
||||
setProfileData(snapshot.val());
|
||||
|
||||
// Populates array with user's interests
|
||||
var interests = snapshot.val().interests || null;
|
||||
if (interests == null) {
|
||||
// Placeholder for no interests specified, will be replaced with default interests
|
||||
interests = "Music, Sports, Movies";
|
||||
}
|
||||
interests = interests.split(",");
|
||||
var interestArray = [];
|
||||
var i = 0;
|
||||
for (var interest in interests) {
|
||||
if (i < 4)
|
||||
interestArray.push(<Interest interest={interests[interest]} />);
|
||||
i++;
|
||||
}
|
||||
setUserInterestArray(interestArray);
|
||||
|
||||
// Populates array with user's rooms
|
||||
var rooms = snapshot.val().rooms;
|
||||
var roomArray = [];
|
||||
for (var room in rooms) {
|
||||
roomArray.push(<ProfileRoom room={rooms[room]} />);
|
||||
}
|
||||
setUserRoomsArray(roomArray);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (user && profileData) {
|
||||
if ("friends" in user) {
|
||||
profileData.uid in user.friends.friends ? setFriends(true) : setFriends(false);
|
||||
}
|
||||
if ("friends" in profileData) {
|
||||
if ("requests" in profileData.friends) {
|
||||
user.uid in profileData.friends.requests ? setPending(true) : setPending(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [user, profileData]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isAuthenticated && (
|
||||
<div className="md:overflow-hidden">
|
||||
{/* Left Side of Page */}
|
||||
<div className="h-dvh md:overflow-hidden">
|
||||
{/* Header */}
|
||||
<Header user={user} />
|
||||
{/* Main Page Section */}
|
||||
<div className="md:grid md:grid-cols-3 mr-2 h-[calc(100%-110px)] pl-5 pr-5 pt-2 max-md:mb-10">
|
||||
<div className="cols-span-1 bg-white shadow-2xl rounded-xl pt-5 max-md:pb-5">
|
||||
{!isEditing && (
|
||||
<div>
|
||||
<img
|
||||
src={profileData.pfp}
|
||||
width="300px"
|
||||
className="relative mx-auto rounded-2xl overflow-hidden"
|
||||
/>
|
||||
<div className="font-bold text-[30px]">
|
||||
{profileData.firstName} {profileData.lastName}
|
||||
</div>
|
||||
<div className="text-[20px]">@{profileData.username}</div>
|
||||
<div className="pt-5">{profileData.bio}</div>
|
||||
<div className="grid grid-cols-3 p-3">
|
||||
{userInterestArray}
|
||||
</div>
|
||||
<div className="grid grid-cols-1 auto-cols-min justify-items-center">
|
||||
{isOwner && (
|
||||
<a
|
||||
onClick={() => {
|
||||
setIsEditing(true);
|
||||
}}
|
||||
className="w-[120px] p-2 cursor-pointer bg-cyan-500 text-white font-bold rounded-full text-center"
|
||||
>
|
||||
Edit Profile
|
||||
</a>
|
||||
)}
|
||||
{(!isOwner && !friends) && (
|
||||
<div>
|
||||
{(!isPending ) && (<a onClick={() => {addFriend(user, profileData.uid);setPending(true)}} className="w-[120px] p-2 cursor-pointer bg-cyan-500 text-white font-bold rounded-full text-center">Add Friend</a>)}
|
||||
{(isPending ) && (<a className="w-[120px] p-2 bg-cyan-500 text-white font-bold rounded-full text-center">Pending</a>)}
|
||||
</div>
|
||||
)}
|
||||
{(!isOwner && friends ) && (<div className="font-bold text-[20px]">Friends</div>)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isEditing && (
|
||||
<ProfileEdit
|
||||
profileData={profileData}
|
||||
user={user}
|
||||
onSave={handleIsEditing}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<div className="h-dvh pb-20 overflow-auto grid md:grid-cols-3 max-md:grid-cols-1 max-md:mt-5 md:pl-5 justify-items-center gap-y-5 gap-1 w-[100%]">
|
||||
{userRoomsArray}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default UserProfile;
|
||||
@@ -0,0 +1,219 @@
|
||||
import Link from "next/link"
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
// Icons
|
||||
import PersonIcon from '@mui/icons-material/Person';
|
||||
|
||||
// Colors for Messages
|
||||
const userColors = [
|
||||
"#ff80ed",
|
||||
"#065535",
|
||||
"#133337",
|
||||
"#ffc0cb",
|
||||
"#e6e6fa",
|
||||
"#ffd700",
|
||||
"#ffa500",
|
||||
"#0000ff",
|
||||
"#1b85b8",
|
||||
"#5a5255",
|
||||
"#559e83",
|
||||
"#ae5a41",
|
||||
"#c3cb71",
|
||||
];
|
||||
|
||||
let dateOptions = {
|
||||
weekday: "long",
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
};
|
||||
|
||||
/**
|
||||
* Rich Message Formatting
|
||||
* @param {String} message - Message to Format
|
||||
* @returns {String} - Formatted Message (IN HTML)
|
||||
*/
|
||||
function RMF(message) {
|
||||
var URLREGEX = /[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g;
|
||||
var URLmatch = message.match(URLREGEX);
|
||||
if (URLmatch) {
|
||||
for (var i = 0; i < URLmatch.length; i++) {
|
||||
var link = (<span>
|
||||
{message.split(URLmatch[i])[0]}
|
||||
<Link href={"https://"+URLmatch[i]} target="_blank" className="hover:underline">{URLmatch[i]}</Link>
|
||||
{message.split(URLmatch[i])[1]}
|
||||
</span>)
|
||||
message = link
|
||||
}
|
||||
}
|
||||
return message
|
||||
}
|
||||
/**
|
||||
* Grabs Window Size
|
||||
* @returns {Object} - Window Size Object (width, height)
|
||||
*/
|
||||
export function useWindowSize() {
|
||||
const [windowSize, setWindowSize] = useState({
|
||||
width: undefined,
|
||||
height: undefined,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
function handleResize() {
|
||||
setWindowSize({
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
});
|
||||
}
|
||||
window.addEventListener("resize", handleResize);
|
||||
handleResize();
|
||||
return () => window.removeEventListener("resize", handleResize);
|
||||
}, []);
|
||||
return windowSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates Color based on string hash
|
||||
* @param {String} user_str - Username / String for hashing
|
||||
* @returns {String} - Color Hex Code Index in userColors
|
||||
*/
|
||||
const generateColor = (user_str) => {
|
||||
// hashes username for consistent colors, maybe all functionality to pick color later
|
||||
let hash = 0;
|
||||
for (let i = 0; i < user_str.length; i++) {
|
||||
hash = user_str.charCodeAt(i) + (hash * 32 - hash);
|
||||
}
|
||||
const index = Math.abs(hash) % userColors.length;
|
||||
return index;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Chat Message Object
|
||||
* @props {JSON} chatObj - Chat Object
|
||||
* @returns {Object} - Chat Message Component
|
||||
*/
|
||||
export function Chat({ chatObj }) {
|
||||
var message = RMF(chatObj.body)
|
||||
return (
|
||||
<div className="width-[100%] bg-white rounded-lg mt-1 text-left p-1 grid grid-cols-2 mr-2">
|
||||
<div>
|
||||
<span style={{ color: userColors[generateColor(chatObj.user)] }}>
|
||||
<Link href={`/user?uid=${chatObj.uid}`}
|
||||
className="hover:font-bold cursor-pointer">
|
||||
{chatObj.user}
|
||||
</Link>
|
||||
</span>
|
||||
: {message}
|
||||
</div>
|
||||
<div className="text-right text-[#d1d1d1]">
|
||||
{new Date(chatObj.timestamp).toLocaleString(dateOptions)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* System Chat Message Object
|
||||
* @prop {JSON} chatObj - Chat Object
|
||||
* @returns {Object} - System Chat Message Component
|
||||
*/
|
||||
export function SystemMessage({ chatObj }) {
|
||||
return (
|
||||
<div className="width-[100%] bg-white rounded-lg mt-1 text-left p-1 grid grid-cols-2 mr-2">
|
||||
<div className="text-[#d1d1d1]">
|
||||
<span style={{ color: userColors[generateColor(chatObj.user)] }}>
|
||||
<Link href={`/user?uid=${chatObj.uid}`}
|
||||
className="hover:font-bold cursor-pointer">
|
||||
{chatObj.user}
|
||||
</Link>
|
||||
</span>{" "}
|
||||
has {chatObj.body} the room.
|
||||
</div>
|
||||
<div className="text-right text-[#d1d1d1]">
|
||||
{new Date(chatObj.timestamp).toLocaleString(dateOptions)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Member Object for Sidebar
|
||||
* @prop {JSON} memberObj - Member Object
|
||||
* @returns {Object} - Member Component
|
||||
*/
|
||||
export function Member({ memberObj }) {
|
||||
return (
|
||||
<Link href={"/user?uid=" + memberObj.uid}>
|
||||
<div className="cursor-pointer g-[aliceblue] rounded-lg m-3 shadow-xl p-2">
|
||||
{memberObj.username}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Chat Room Object for Sidebar
|
||||
* @prop {JSON} roomObj - Room Object
|
||||
* @returns {Object} - Chat Room Component
|
||||
*/
|
||||
export function ChatRoomSidebar({ roomObj }) {
|
||||
const [isRoomHovered, setIsRoomHovered] = useState(false);
|
||||
|
||||
if ("users" in roomObj) {
|
||||
if ("online" in roomObj.users) {
|
||||
var roomOnline = Object.keys(roomObj.users.online).length
|
||||
} else {
|
||||
var roomOnline = 0
|
||||
}
|
||||
if ("all" in roomObj.users) {
|
||||
var roomTotal = Object.keys(roomObj.users.all).length
|
||||
} else {
|
||||
var roomTotal = 0
|
||||
}
|
||||
} else {
|
||||
var roomOnline = 0
|
||||
var roomTotal = 0
|
||||
}
|
||||
const handleMouseEnter = () => {
|
||||
setIsRoomHovered(true);
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
setIsRoomHovered(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="border-[black] border-1 shadow-lg p-2 m-2 rounded-lg cursor-pointer">
|
||||
<Link href={`/chat?room=${roomObj.path}/${roomObj.name}-${roomObj.timestamp}`}>
|
||||
<div className="grid grid-cols-3">
|
||||
<div onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
|
||||
<PersonIcon/>
|
||||
{isRoomHovered && roomObj.users && (
|
||||
<div
|
||||
className="fixed bg-white p-2 shadow-md"
|
||||
>
|
||||
<ul>
|
||||
{Object.values(roomObj.users.all).map((user, index) => ( // I hate making lists like this
|
||||
<li key={index}>{user.username}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
<div>{roomOnline} / {roomTotal}</div>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<div className="font-bold">{roomObj.name}</div>
|
||||
<div className="italic">{roomObj.description}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// This will be removed once dateOptions is no longer used in this file
|
||||
export { dateOptions };
|
||||
@@ -0,0 +1,57 @@
|
||||
// Firebase Imports
|
||||
import { database } from "../../../../firebase-config"
|
||||
import { ref, set, get } from "firebase/database";
|
||||
|
||||
import ChatIcon from '@mui/icons-material/Chat';
|
||||
|
||||
|
||||
export function openDM(user, uid) {
|
||||
var uid1 = user.uid < uid? user.uid : uid
|
||||
var uid2 = user.uid > uid? user.uid : uid
|
||||
get(ref(database, `dms/${uid1}-${uid2}`)).then((snapshot) => {
|
||||
if (snapshot.exists()) {
|
||||
window.location.href = `/dm?dm=${uid1}-${uid2}`
|
||||
} else {
|
||||
createDM(user, uid)
|
||||
window.location.href = `/dm?dm=${uid1}-${uid2}`
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export function createDM(user, uid) {
|
||||
var uid1 = user.uid < uid? user.uid : uid
|
||||
var uid2 = user.uid > uid? user.uid : uid
|
||||
set(ref(database, `dms/${uid1}-${uid2}`), {
|
||||
initUID: user.uid,
|
||||
targetUID: uid,
|
||||
room: uid1 + '-' + uid2,
|
||||
UIDs: [user.uid, uid]
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {JSON} friendObj - Friend Object (user)
|
||||
* @returns DM Component
|
||||
*/
|
||||
export function DM({user,friendObj}) {
|
||||
return (
|
||||
<div className="border-[black] border-1 shadow-lg m-2 rounded-lg">
|
||||
<div className='grid grid-cols-4'>
|
||||
<div className='place-content-center'>
|
||||
<ChatIcon className='cursor-pointer' onClick={() => {openDM(user,friendObj.uid)}}/>
|
||||
</div>
|
||||
<div className='col-span-3 cursor-pointer'>
|
||||
<div onClick={() => {openDM(user,friendObj.uid)}}>
|
||||
<div className='inline-block mr-5'><img src={friendObj.pfp} className='w-[50px]'/></div>
|
||||
<div className='inline-block relative top-[-6px]'>
|
||||
<div className="font-bold">{friendObj.firstName} {friendObj.lastName}</div>
|
||||
<div className="">@{friendObj.username}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import Link from 'next/link'
|
||||
|
||||
// Icons
|
||||
import ChatIcon from '@mui/icons-material/Chat';
|
||||
|
||||
// Firebase Imports
|
||||
import { database } from "../../../../firebase-config"
|
||||
import { ref, set } from "firebase/database";
|
||||
|
||||
import { openDM } from './dm';
|
||||
|
||||
// Icons
|
||||
import CheckIcon from '@mui/icons-material/Check';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
|
||||
/**
|
||||
* Send a friend request to a user
|
||||
* @param {JSON} user - User Object
|
||||
* @param {JSON} uid - User ID of the user to send a friend request to
|
||||
*/
|
||||
export function addFriend(user, uid) {
|
||||
// Add to user's friend requests
|
||||
set(ref(database, `users/${uid}/friends/requests/${user.uid}`), {
|
||||
uid: user.uid
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {JSON} friendObj - Friend Object (user)
|
||||
* @returns Friend Component
|
||||
*/
|
||||
export function Friend({user,friendObj}) {
|
||||
return (
|
||||
<div className="border-[black] border-1 shadow-lg m-2 rounded-lg">
|
||||
<div className='grid grid-cols-4'>
|
||||
<div className='place-content-center'>
|
||||
<ChatIcon className='cursor-pointer' onClick={() => {openDM(user,friendObj.uid)}}/>
|
||||
</div>
|
||||
<div className='col-span-3 cursor-pointer'>
|
||||
<Link href={`/user?uid=${friendObj.uid}`}>
|
||||
<div className='inline-block mr-5'><img src={friendObj.pfp} className='w-[50px]'/></div>
|
||||
<div className='inline-block relative top-[-6px]'>
|
||||
<div className="font-bold">{friendObj.firstName} {friendObj.lastName}</div>
|
||||
<div className="">@{friendObj.username}</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @prop {JSON} user - User Object
|
||||
* @prop {JSON} requestingUser - User Object of the user requesting to be friends
|
||||
* @returns
|
||||
*/
|
||||
export function FriendRequest({user, requestingUser}) {
|
||||
/**
|
||||
* Accepts Friend Request
|
||||
* @param {JSON} user - User Object
|
||||
* @param {JSON} uid - User ID of the user who sent the friend request
|
||||
*/
|
||||
function acceptRequest(user, uid) {
|
||||
// Add to user's friends
|
||||
set(ref(database, `users/${user.uid}/friends/friends/${uid}`), {
|
||||
uid: uid
|
||||
})
|
||||
set(ref(database, `users/${uid}/friends/friends/${user.uid}`), {
|
||||
uid: user.uid
|
||||
})
|
||||
removeRequest(user, uid)
|
||||
}
|
||||
/**
|
||||
* Removes Friend Request
|
||||
* @param {JSON} user - User Object
|
||||
* @param {JSON} uid - User ID of the user who sent the friend request
|
||||
*/
|
||||
function removeRequest(user, uid) {
|
||||
// Remove from user's friend requests
|
||||
set(ref(database, `users/${user.uid}/friends/requests/${uid}`), null)
|
||||
}
|
||||
return (
|
||||
<div className="border-[black] border-1 shadow-lg m-2 rounded-lg">
|
||||
<div className='grid grid-cols-4'>
|
||||
<div className='place-content-center'>
|
||||
<CheckIcon className='cursor-pointer ml-5' onClick={() => {acceptRequest(user, requestingUser.uid)}}/>
|
||||
<CloseIcon className='cursor-pointer ml-5' onClick={() => {removeRequest(user, requestingUser.uid)}}/>
|
||||
</div>
|
||||
<div className='col-span-3 cursor-pointer'>
|
||||
<Link href={`/user?uid=${requestingUser.uid}`}>
|
||||
<div className='inline-block mr-5'><img src={requestingUser.pfp} className='w-[50px]'/></div>
|
||||
<div className='inline-block relative top-[-6px]'>
|
||||
<div className="font-bold">{requestingUser.firstName} {requestingUser.lastName}</div>
|
||||
<div className="">@{requestingUser.username}</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// Dependency Imports
|
||||
import { Form, useForm } from "react-hook-form";
|
||||
|
||||
// Firebase Imports
|
||||
import { ref, set } from "firebase/database";
|
||||
import { database } from "../../../../firebase-config";
|
||||
|
||||
// Component Imports
|
||||
import { Chat, SystemMessage } from "../datatypes";
|
||||
|
||||
// Icons
|
||||
import SendIcon from '@mui/icons-material/Send';
|
||||
|
||||
/**
|
||||
* Chat Room Component
|
||||
* @prop {JSON} roomObj - Room Object
|
||||
* @prop {JSON} user - User Object
|
||||
* @returns {Object} - Chat Room Component
|
||||
*/
|
||||
export function DMRoom({ roomObj, user }) {
|
||||
var { register, control, reset, handleSubmit } = useForm();
|
||||
|
||||
// Message updater
|
||||
var chatsArr = [];
|
||||
var messages = roomObj.chats;
|
||||
for (var message in messages) {
|
||||
if (messages[message].isSystem) {
|
||||
chatsArr.push(
|
||||
<SystemMessage
|
||||
chatObj={messages[message]}
|
||||
key={messages[message].timestamp}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
chatsArr.push(
|
||||
<Chat
|
||||
chatObj={messages[message]}
|
||||
key={messages[message].timestamp}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
var chats = chatsArr.reverse();
|
||||
|
||||
/**
|
||||
* Send Message in Chatroom
|
||||
* @param {JSON} data - Message data to send (from form)
|
||||
* @returns {void}
|
||||
*/
|
||||
function sendMessage(data) {
|
||||
reset();
|
||||
var payload = {
|
||||
body: data.message,
|
||||
user: user.username,
|
||||
uid: user.uid,
|
||||
isSystem: false,
|
||||
timestamp: new Date().getTime(),
|
||||
};
|
||||
set(
|
||||
ref(
|
||||
database,
|
||||
`/dms/${roomObj.room}/chats/${new Date().getTime()}-${user.username}`
|
||||
),
|
||||
payload
|
||||
);
|
||||
}
|
||||
|
||||
if (!chats) return <div>No Chats</div>;
|
||||
return (
|
||||
<div className="m-1 h-[100%] rounded-lg">
|
||||
<div className="h-[90%] m-4 overflow-y-auto flex flex-col-reverse">
|
||||
{chats}
|
||||
</div>
|
||||
<div className="m-2 h-[10%] w-[100%] bg-white rounded-lg">
|
||||
<Form
|
||||
onSubmit={handleSubmit(sendMessage)}
|
||||
control={control}
|
||||
>
|
||||
<div className="width-[100%] grid grid-cols-6 pr-5 pt-1">
|
||||
<input type="text" {...register("message")} placeholder="Enter message..." className="col-span-5 border-[0px]" />
|
||||
<button className="p-2 cursor-pointer bg-cyan-500 text-white font-bold rounded-full mr-5 w-[100%]"><SendIcon/></button>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
// System Imports
|
||||
import Link from "next/link"
|
||||
|
||||
// Firebase Imports
|
||||
import { database } from "../../../firebase-config";
|
||||
import { ref, set, remove } from "firebase/database";
|
||||
|
||||
// Component Imports
|
||||
import { NotificationPanel } from "./notifications/notifications";
|
||||
import { ProfilePanel } from "./profile/ProfilePanel"
|
||||
|
||||
// Icons
|
||||
import MenuIcon from '@mui/icons-material/Menu';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import RemoveIcon from '@mui/icons-material/Remove';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
|
||||
/**
|
||||
* Closes Chat
|
||||
* @param {JSON} chatRoomObj - Chat Room Object
|
||||
* @param {JSON} user - User Object
|
||||
* @returns {void}
|
||||
*/
|
||||
function closeChat(chatRoomObj, user) {
|
||||
remove(ref(database, `/rooms/${chatRoomObj.path}/${chatRoomObj.name}-${chatRoomObj.timestamp}/users/online/${user.uid}`))
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds Chat Room to My Rooms
|
||||
* @param {JSON} chatRoomObj - Chat Room Object
|
||||
* @param {JSON} user - User Object
|
||||
* @returns {void}
|
||||
*/
|
||||
function addToMyRooms(chatRoomObj, user) {
|
||||
set(
|
||||
ref(
|
||||
database,
|
||||
`/users/${user.uid}/rooms/${chatRoomObj.name}-${chatRoomObj.timestamp}`
|
||||
),
|
||||
{
|
||||
name: chatRoomObj.name,
|
||||
path: chatRoomObj.path,
|
||||
timestamp: chatRoomObj.timestamp,
|
||||
description: chatRoomObj.description,
|
||||
longitude: chatRoomObj.longitude,
|
||||
latitude: chatRoomObj.latitude,
|
||||
}
|
||||
);
|
||||
var path =
|
||||
chatRoomObj.path + "/" + chatRoomObj.name + "-" + chatRoomObj.timestamp;
|
||||
set(ref(database, `/rooms/${path}/users/all/${user.uid}`), user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes Chat Room from My Rooms
|
||||
* @param {JSON} chatRoomObj - Chat Room Object
|
||||
* @param {JSON} user - User Object
|
||||
* @returns {void}
|
||||
*/
|
||||
function removeFromMyRooms(chatRoomObj, user) {
|
||||
var path =
|
||||
chatRoomObj.path + "/" + chatRoomObj.name + "-" + chatRoomObj.timestamp;
|
||||
remove(
|
||||
ref(
|
||||
database,
|
||||
`/users/${user.uid}/rooms/${chatRoomObj.name}-${chatRoomObj.timestamp}`
|
||||
)
|
||||
);
|
||||
remove(ref(database, `/rooms/${path}/users/all/${user.uid}`));
|
||||
}
|
||||
|
||||
/**
|
||||
* Header Component
|
||||
* @prop {String} mainTab - Main Tab
|
||||
* @prop {JSON} chatRoomObj - Chat Room Object
|
||||
* @prop {JSON} user - User Object
|
||||
*/
|
||||
export function Header({mainTab,chatRoomObj,user,sidebarControl}) {
|
||||
|
||||
if (mainTab == "chat") {
|
||||
var roomName = chatRoomObj.name + "-" + chatRoomObj.timestamp;
|
||||
if (user.rooms != null && roomName in user.rooms) {
|
||||
// its in there
|
||||
var isMyRoom = true;
|
||||
} else {
|
||||
// its not in there
|
||||
var isMyRoom = false;
|
||||
}
|
||||
|
||||
}
|
||||
return (
|
||||
<div className="flex m-2 rounded-lg h-[63px] bg-white shadow-2xl p-1">
|
||||
<div className="flex shrink h-[60px]">
|
||||
<Link href="/app">
|
||||
<img src="/logos/logo_transparent_inverse.png" className="h-[60px] max-xl:hidden" />
|
||||
<img src="/logos/icon.png" className="h-[50px] mt-[5px] mb-[5px] xl:hidden" />
|
||||
</Link>
|
||||
</div>
|
||||
<div className="grow grid grid-rows-1 grid-flow-col auto-cols-max justify-end gap-2 h-[60px] p-2">
|
||||
{mainTab == "chat" && isMyRoom == false && (
|
||||
<a
|
||||
onClick={() => {
|
||||
addToMyRooms(chatRoomObj, user);
|
||||
|
||||
}}
|
||||
className="p-2 cursor-pointer bg-cyan-500 text-white font-bold rounded-full mr-2 flex items-center"
|
||||
>
|
||||
<AddIcon/>
|
||||
</a>
|
||||
)}
|
||||
{mainTab == "chat" && isMyRoom == true && (
|
||||
<a
|
||||
onClick={() => {
|
||||
removeFromMyRooms(chatRoomObj, user);
|
||||
|
||||
}}
|
||||
className="p-2 cursor-pointer bg-cyan-500 text-white font-bold rounded-full mr-2 flex items-center"
|
||||
>
|
||||
<RemoveIcon/>
|
||||
</a>
|
||||
)}
|
||||
{(mainTab == "chat" || mainTab == "dm" ) && (
|
||||
<Link
|
||||
href="/app"
|
||||
className="p-2 cursor-pointer bg-cyan-500 text-white font-bold rounded-full mr-2 flex items-center"
|
||||
onClick={() => {closeChat(chatRoomObj,user)}}
|
||||
>
|
||||
<CloseIcon/>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{/* Notifications Panel */}
|
||||
<NotificationPanel user={user}/>
|
||||
|
||||
{/*Profile Dropdown */}
|
||||
<ProfilePanel user={user}/>
|
||||
|
||||
{/* Sidebar Control (for small screens) */}
|
||||
<div
|
||||
className="md:hidden p-2 cursor-pointer bg-cyan-500 text-white font-bold rounded-full mr-5 flex items-center"
|
||||
onClick={() => {sidebarControl()}}
|
||||
>
|
||||
<MenuIcon/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import { Map, Marker, ZoomControl } from "pigeon-maps";
|
||||
import { database } from "../../../../firebase-config";
|
||||
import { ref, get } from "firebase/database";
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
// ONLY nearby markers
|
||||
function NearbyRoomMarkers({ loc, user }) {
|
||||
const [markerArr, setMarkerArr] = useState([]);
|
||||
|
||||
// Room path in DB
|
||||
const path =
|
||||
String(loc.latitude.toFixed(2)).replace(".", "") +
|
||||
"/" +
|
||||
String(loc.longitude.toFixed(2)).replace(".", "") +
|
||||
"/";
|
||||
|
||||
// Sorry for the href but <Link> doesn't work here
|
||||
const handleRoomMarkerClick = (roomObj) => {
|
||||
window.location.href =
|
||||
"/chat?room=" + path + roomObj.name + "-" + roomObj.timestamp;
|
||||
};
|
||||
|
||||
// Mostly copied Nick's code from before
|
||||
useEffect(() => {
|
||||
if (loc && user) {
|
||||
get(ref(database, `/rooms/${path}`)).then((snapshot) => {
|
||||
if (snapshot.exists()) {
|
||||
const rooms = snapshot.val();
|
||||
|
||||
const newMarkers = Object.values(rooms).map((roomObj, index) => {
|
||||
const markerKey = roomObj.path + "-" + index;
|
||||
return (
|
||||
// Want to change this to be something other than markers (or something extra)
|
||||
<Marker
|
||||
key={markerKey}
|
||||
anchor={[roomObj.latitude, roomObj.longitude]}
|
||||
color="blue"
|
||||
onClick={() => handleRoomMarkerClick(roomObj)}
|
||||
></Marker>
|
||||
);
|
||||
});
|
||||
setMarkerArr(newMarkers);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
return markerArr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Geo Component for Wrapping Map
|
||||
* @constructor
|
||||
* @prop {JSON} loc - Location Object {latitude, longitude}
|
||||
* @prop {Number} zoom - Zoom Level
|
||||
* @prop {Boolean} moveable - Moveable Map
|
||||
* @prop {Boolean} markers - Enable Markers
|
||||
* @returns {Map} - Geo Component (As Map)
|
||||
*/
|
||||
export function Geo({ loc, zoom, moveable, markers, user }) {
|
||||
const handleUserMarkerClick = () => {
|
||||
window.location.href = "/user?uid=" + user.uid;
|
||||
}
|
||||
|
||||
if (!loc) {
|
||||
return <div>Getting Location...</div>;
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<Map
|
||||
center={[loc.latitude, loc.longitude]}
|
||||
defaultZoom={zoom}
|
||||
mouseEvents={moveable}
|
||||
touchEvents={moveable}
|
||||
>
|
||||
{zoom && <ZoomControl />}
|
||||
{markers && NearbyRoomMarkers({ loc, user })}
|
||||
{user && ( // Shows the user marker
|
||||
<Marker anchor={[loc.latitude, loc.longitude]} color="red" onClick={handleUserMarkerClick} />
|
||||
)}
|
||||
</Map>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
// System Imports
|
||||
import { Popover } from "@headlessui/react";
|
||||
|
||||
// Icon Imports
|
||||
import NotificationsIcon from '@mui/icons-material/Notifications';
|
||||
import NotificationsPausedIcon from '@mui/icons-material/NotificationsPaused';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
|
||||
// Firebase Imports
|
||||
import { database } from "../../../../firebase-config";
|
||||
import { ref, set, remove } from "firebase/database";
|
||||
|
||||
/**
|
||||
* Notification Object
|
||||
* @constructor
|
||||
* @prop {user.notification} data - Notification data
|
||||
* @returns {Notification} - Notification Component
|
||||
*/
|
||||
function Notification({data}) {
|
||||
return (
|
||||
<div className="hover:bg-[#C0C0C0] rounded-lg">
|
||||
<div className="float-right top-0 cursor-pointer p-2 text-[24px] text-slate-500">
|
||||
<div onClick={() => {removeNotification(data.id)}}><CloseIcon/></div>
|
||||
</div>
|
||||
<div className="p-3 text-left">
|
||||
{data.title}<br/>
|
||||
{data.byline}<br/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes Notification
|
||||
* @param {String} ruser - Receiving user UID (User Whose Notifications are being removed)
|
||||
* @param {String} dataID - Notification ID
|
||||
* @returns {void}
|
||||
*/
|
||||
function removeNotification(ruser, dataID) {
|
||||
remove(ref(database, `/user/${ruser}/notifications/${dataID}`))
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates New Notification
|
||||
* @param {String} title - Title of the notification
|
||||
* @param {String} byline - Byline of the notification
|
||||
* @param {String} action - Action to perform (friend request [fr], more to come)
|
||||
* @param {String} suser - Sending user UID
|
||||
* @param {String} ruser - Receiving user UID
|
||||
* @returns {void}
|
||||
*/
|
||||
function createNotification(title, byline, action, suser, ruser) {
|
||||
var timestamp = new Date().getTime();
|
||||
var payload = {
|
||||
title: title,
|
||||
byline: byline,
|
||||
action: action,
|
||||
suser: suser,
|
||||
ruser: ruser
|
||||
};
|
||||
set(ref(database, `/user/${ruser}/notifications/${timestamp}-${suser}`), payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notification Panel
|
||||
* @constructor
|
||||
* @prop {user} user - User object (from Firebase)
|
||||
* @returns {NotificationPanel} - Notification Panel Component
|
||||
*/
|
||||
export function NotificationPanel({user}) {
|
||||
var notificationsMap = []
|
||||
if (user.notification) {
|
||||
for (var notificationPackage in user.notification) {
|
||||
notificationsMap.push(<Notification data={user.notification[notificationPackage]}/>)
|
||||
}
|
||||
var isNotifications = true
|
||||
} else {
|
||||
var isNotifications = false
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover className="relative">
|
||||
<Popover.Button as="div">
|
||||
<div className="h-[44px] p-[8px] cursor-pointer bg-cyan-500 text-white font-bold rounded-full shadow-2xl flex items-center">
|
||||
<NotificationsIcon />
|
||||
</div>
|
||||
</Popover.Button>
|
||||
|
||||
<Popover.Panel className="absolute z-10 bg-white mt-[4px] rounded-xl ml-3 shadow-2xl w-64 md:right-[0px] max-md:right-[-300%]">
|
||||
<div className="grid grid-cols-1">
|
||||
{isNotifications && notificationsMap}
|
||||
{!isNotifications &&
|
||||
<div className="h-[64px] flex flex-col justify-center items-center">
|
||||
<NotificationsPausedIcon/> All caught up!
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</Popover.Panel>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// Dependency Imports
|
||||
import { Form, useForm } from "react-hook-form";
|
||||
|
||||
// Firebase Imports
|
||||
import { ref, set } from "firebase/database";
|
||||
import { database } from "../../../../firebase-config";
|
||||
|
||||
// Component Imports
|
||||
import { Chat, SystemMessage } from "../datatypes";
|
||||
|
||||
// Icons
|
||||
import SendIcon from '@mui/icons-material/Send';
|
||||
|
||||
/**
|
||||
* Chat Room Component
|
||||
* @prop {JSON} roomObj - Room Object
|
||||
* @prop {JSON} user - User Object
|
||||
* @returns {Object} - Chat Room Component
|
||||
*/
|
||||
export function ChatRoom({ roomObj, user }) {
|
||||
var { register, control, reset, handleSubmit } = useForm();
|
||||
|
||||
// Message updater
|
||||
var chatsArr = [];
|
||||
var messages = roomObj.chats;
|
||||
for (var message in messages) {
|
||||
if (messages[message].isSystem) {
|
||||
chatsArr.push(
|
||||
<SystemMessage
|
||||
chatObj={messages[message]}
|
||||
key={messages[message].timestamp}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
chatsArr.push(
|
||||
<Chat
|
||||
chatObj={messages[message]}
|
||||
key={messages[message].timestamp}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
var chats = chatsArr.reverse();
|
||||
|
||||
/**
|
||||
* Send Message in Chatroom
|
||||
* @param {JSON} data - Message data to send (from form)
|
||||
* @returns {void}
|
||||
*/
|
||||
function sendMessage(data) {
|
||||
reset();
|
||||
var payload = {
|
||||
body: data.message,
|
||||
user: user.username,
|
||||
uid: user.uid,
|
||||
isSystem: false,
|
||||
timestamp: new Date().getTime(),
|
||||
};
|
||||
set(
|
||||
ref(
|
||||
database,
|
||||
`/rooms/${
|
||||
roomObj.path + "/" + roomObj.name + "-" + roomObj.timestamp
|
||||
}/chats/${new Date().getTime()}-${user.username}`
|
||||
),
|
||||
payload
|
||||
);
|
||||
}
|
||||
|
||||
if (!chats) return <div>No Chats</div>;
|
||||
return (
|
||||
<div className="m-1 h-[100%] rounded-lg">
|
||||
<div className="h-[90%] m-4 overflow-y-auto flex flex-col-reverse">
|
||||
{chats}
|
||||
</div>
|
||||
<div className="m-2 h-[10%] w-[100%] bg-white rounded-lg">
|
||||
<Form
|
||||
onSubmit={handleSubmit(sendMessage)}
|
||||
control={control}
|
||||
>
|
||||
<div className="width-[100%] grid grid-cols-6 pr-5 pt-1">
|
||||
<input type="text" {...register("message")} placeholder="Enter message..." className="col-span-5 border-[0px]" />
|
||||
<button className="p-2 cursor-pointer bg-cyan-500 text-white font-bold rounded-full mr-5 w-[100%]"><SendIcon/></button>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { Geo } from "../map/geo";
|
||||
|
||||
/**
|
||||
* Module for Welcome Message on main tab landing page
|
||||
* @prop {JSON} user - User Object
|
||||
* @returns {Object} - Welcome Message Component
|
||||
*/
|
||||
function WelcomeMessage({ user }) {
|
||||
return (
|
||||
<div className="bg-white rounded-lg m-2 mt-4 text-left p-2 pl-5">
|
||||
<div>
|
||||
Welcome, {user.firstName} {user.lastName} ({user.username})
|
||||
</div>
|
||||
<div>Lets see what's happening in your area.</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary App Landing Page
|
||||
* @prop {JSON} loc - Location Object {latitude, longitude}
|
||||
* @prop {Markers[]} markers - Array of Markers
|
||||
* @prop {JSON} user - User Object
|
||||
* @returns {Object} - Home Page Component
|
||||
*/
|
||||
export function HomePage({ loc, user }) {
|
||||
return (
|
||||
<>
|
||||
<WelcomeMessage user={user} />
|
||||
<div className="h-[calc(100%-110px)] m-5 rounded-lg">
|
||||
<Geo
|
||||
loc={loc}
|
||||
zoom={14}
|
||||
moveable={true}
|
||||
markers={true}
|
||||
user={user}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Interest for Profile
|
||||
* @prop {String} interest - Interest item
|
||||
* @returns {Object} - Interest Component
|
||||
*/
|
||||
export function Interest({ interest }) {
|
||||
return (
|
||||
<div>
|
||||
<div className="rounded-lg m-2 p-2 shadow-xl">{interest}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
// System Imports
|
||||
import { useForm, Form } from "react-hook-form";
|
||||
|
||||
// Firebase Imports
|
||||
import { database, storage } from "../../../../firebase-config";
|
||||
import { ref as sRef, getDownloadURL,uploadBytes } from "firebase/storage";
|
||||
import { ref, update } from "firebase/database";
|
||||
|
||||
|
||||
/**
|
||||
* Profile Edit Component
|
||||
* @prop {JSON} profileData - Profile Data
|
||||
* @prop {JSON} user - User Object
|
||||
* @prop {Function} onSave - Save Function
|
||||
* @returns {Object} - Profile Edit Component
|
||||
*/
|
||||
export function ProfileEdit({ profileData, user, onSave }) {
|
||||
var { register, control } = useForm();
|
||||
|
||||
const handleEditState = () => {
|
||||
onSave(false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles clicking save button
|
||||
* @prop {JSON} data - Data to save
|
||||
*/
|
||||
function save({ data }) {
|
||||
// Profile pic handling
|
||||
if (data.pfp[0]) {
|
||||
// image stuff
|
||||
uploadBytes(sRef(storage, `users/${user.uid}/pfp`), data.pfp[0]).then(
|
||||
() => {
|
||||
getDownloadURL(sRef(storage, `users/${user.uid}/pfp`)).then((url) => {
|
||||
data.pfp = url;
|
||||
for (var key in data) {
|
||||
if (data[key] == "") {
|
||||
data[key] = profileData[key];
|
||||
}
|
||||
}
|
||||
|
||||
handleEditState(false);
|
||||
update(ref(database, `users/${user.uid}`), data);
|
||||
});
|
||||
}
|
||||
);
|
||||
} else {
|
||||
for (var key in data) {
|
||||
if (data[key] == "") {
|
||||
data[key] = profileData[key];
|
||||
}
|
||||
}
|
||||
data.pfp = profileData.pfp;
|
||||
handleEditState(false);
|
||||
update(ref(database, `users/${user.uid}`), data);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Form onSubmit={save} encType={"application/json"} control={control}>
|
||||
<div className="grid grid-cols-2">
|
||||
<div>
|
||||
<img
|
||||
src={profileData.pfp}
|
||||
width="150px"
|
||||
className="relative mx-auto rounded-2xl overflow-hidden"
|
||||
/>
|
||||
Current Profile Picture
|
||||
</div>
|
||||
<div className="flex content-center">
|
||||
<input
|
||||
type="file"
|
||||
{...register("pfp")}
|
||||
className="w-[80%]"
|
||||
accept=".jpg,.png,.jpeg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 pl-2 w-[90%]">
|
||||
<div className="pt-5">
|
||||
<div className="font-bold">First Name</div>
|
||||
<input
|
||||
className="w-[80%] border-2 border-gray-300 p-2 rounded-lg"
|
||||
type="text"
|
||||
{...register("firstName")}
|
||||
placeholder={profileData.firstName}
|
||||
/>
|
||||
</div>
|
||||
<div className="pt-5">
|
||||
<div className="font-bold">Last Name</div>
|
||||
<input
|
||||
className="w-[80%] border-2 border-gray-300 p-2 rounded-lg"
|
||||
type="text"
|
||||
{...register("lastName")}
|
||||
placeholder={profileData.lastName}
|
||||
/>
|
||||
</div>
|
||||
<div className="pt-5">
|
||||
<div className="font-bold">Username</div>
|
||||
<input
|
||||
className="w-[80%] border-2 border-gray-300 p-2 rounded-lg"
|
||||
type="text"
|
||||
{...register("username")}
|
||||
placeholder={profileData.username}
|
||||
/>
|
||||
</div>
|
||||
<div className="pt-5">
|
||||
<div className="font-bold">Interests (Comma Seperated)</div>
|
||||
<input
|
||||
className="w-[80%] border-2 border-gray-300 p-2 rounded-lg"
|
||||
type="text"
|
||||
{...register("interests")}
|
||||
placeholder={profileData.interests}
|
||||
/>
|
||||
</div>
|
||||
<div className="pt-5 col-span-2">
|
||||
<div className="font-bold">Bio</div>
|
||||
<textarea
|
||||
className="w-[92%] border-2 border-gray-300 p-2 rounded-lg"
|
||||
{...register("bio")}
|
||||
type="text"
|
||||
placeholder={profileData.bio}
|
||||
/>
|
||||
</div>
|
||||
<div className="justify-items-center pt-5 col-span-2">
|
||||
<button
|
||||
type="submit"
|
||||
className="p-2 cursor-pointer bg-cyan-500 text-white font-bold rounded-full text-center"
|
||||
>
|
||||
{" "}
|
||||
Save Changes{" "}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// System Imports
|
||||
import { Popover } from "@headlessui/react";
|
||||
import Link from "next/link"
|
||||
|
||||
// Firebase Imports
|
||||
import { auth } from "../../../../firebase-config";
|
||||
import { signOut } from "firebase/auth";
|
||||
|
||||
/**
|
||||
* Logs out from Firebase Authentication
|
||||
* @returns {void}
|
||||
*/
|
||||
function logout() {
|
||||
signOut(auth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Profile Panel Component
|
||||
* @prop {JSON} user - User Object
|
||||
* @returns {Object} - Profile Panel Component
|
||||
*/
|
||||
export function ProfilePanel({user}) {
|
||||
return (
|
||||
<Popover className="relative">
|
||||
<Popover.Button as="div">
|
||||
<div className="h-[44px] p-[2px] pr-[15px] cursor-pointer bg-cyan-500 text-white font-bold rounded-full shadow-2xl flex">
|
||||
<div className="flex items-center pl-1">{user.firstName}</div>
|
||||
<div className="ml-3 rounded-lg">
|
||||
<img
|
||||
src={user.pfp}
|
||||
width="40px"
|
||||
className="relative mx-auto rounded-xl overflow-hidden"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Popover.Button>
|
||||
|
||||
<Popover.Panel className="absolute z-10 bg-white mt-[4px] rounded-xl ml-3 shadow-2xl">
|
||||
<div className="grid grid-cols-1">
|
||||
<Link
|
||||
className="rounded-xl p-4 hover:bg-[#C0C0C0]"
|
||||
href={"/user?uid=" + user.uid}
|
||||
>
|
||||
View Profile
|
||||
</Link>
|
||||
<Link
|
||||
className="rounded-xl p-4 hover:bg-[#C0C0C0]"
|
||||
onClick={logout}
|
||||
href="/"
|
||||
>
|
||||
Sign Out
|
||||
</Link>
|
||||
</div>
|
||||
</Popover.Panel>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// System Imports
|
||||
import { Geo } from "../map/geo";
|
||||
import Link from "next/link"
|
||||
|
||||
// Component Imports
|
||||
import { dateOptions } from "../datatypes";
|
||||
|
||||
/**
|
||||
* Profile Room Component
|
||||
* @prop {JSON} room - Room Object
|
||||
* @returns {Object} - Profile Room Component
|
||||
*/
|
||||
export function ProfileRoom({ room }) {
|
||||
return (
|
||||
<div className="rounded-lg p-2 shadow-xl bg-white h-[250px] w-[100%]">
|
||||
<div className="relative z-1 h-[235px] opacity-50">
|
||||
<Geo
|
||||
loc={{ latitude: room.latitude, longitude: room.longitude }}
|
||||
zoom={12}
|
||||
moveable={false}
|
||||
markers={false}
|
||||
/>
|
||||
</div>
|
||||
<div className="relative z-2 top-[-235px] text-left p-2">
|
||||
<div className="text-2xl font-bold">{room.name}</div>
|
||||
<div>{room.description}</div>
|
||||
<div>
|
||||
Created on {new Date(room.timestamp).toLocaleString(dateOptions)}
|
||||
</div>
|
||||
<Link
|
||||
href={
|
||||
"/chat?room=" + room.path + "/" + room.name + "-" + room.timestamp
|
||||
}
|
||||
className="absolute z-2 top-[190px] w-[108px] p-2 cursor-pointer bg-cyan-500 text-white font-bold rounded-full flex items-center"
|
||||
>
|
||||
Open Room
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// Component Imports
|
||||
import { Geo } from "../map/geo";
|
||||
import { Member } from "../datatypes"
|
||||
|
||||
// Sidebar when in a Chatrooms
|
||||
/**
|
||||
* Sidebar while in Chatroom
|
||||
* @prop {JSON} chatRoomObj - Chatroom Object
|
||||
* @returns {Object} - Sidebar Component
|
||||
*/
|
||||
export function Sidebar({chatRoomObj}) {
|
||||
// Active users list
|
||||
if (
|
||||
chatRoomObj.hasOwnProperty("users") &&
|
||||
chatRoomObj.users.hasOwnProperty("online")
|
||||
) {
|
||||
var activeUsers = [];
|
||||
var activeUsersJSON = chatRoomObj.users.online;
|
||||
for (var user in activeUsersJSON)
|
||||
activeUsers.push(<Member memberObj={activeUsersJSON[user]} />);
|
||||
var chatroomOnline = activeUsers
|
||||
}
|
||||
|
||||
// Users who added to "my rooms"
|
||||
if (
|
||||
chatRoomObj.hasOwnProperty("users") &&
|
||||
chatRoomObj.users.hasOwnProperty("all")
|
||||
) {
|
||||
var allUsers = [];
|
||||
var allUsersJSON = chatRoomObj.users.all;
|
||||
for (var user in allUsersJSON)
|
||||
allUsers.push(<Member memberObj={allUsersJSON[user]} />);
|
||||
var chatroomUsers = allUsers
|
||||
}
|
||||
return (
|
||||
<div className="overflow-hidden h-dvh">
|
||||
<div className="m-2 h-[98%] grid grid-cols-1">
|
||||
<div className="bg-white rounded-lg m-2 shadow-2xl relative">
|
||||
<div className="w-[100%] h-[100%] opacity-50 absolute rounded-lg z-10">
|
||||
<Geo
|
||||
loc={{
|
||||
latitude: parseFloat(chatRoomObj.latitude.toFixed(2)),
|
||||
longitude: parseFloat(chatRoomObj.longitude.toFixed(2)),
|
||||
}}
|
||||
zoom={12}
|
||||
moveable={false}
|
||||
markers={false}
|
||||
/>
|
||||
</div>
|
||||
<div className="z-10 top-0 left-0 w-[100%] h-[100%] absolute text-left pl-3 pt-2">
|
||||
<span className="font-bold text-[24px]">{chatRoomObj.name}</span>
|
||||
<br />
|
||||
{chatRoomObj.description}
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg m-2 shadow-2xl">
|
||||
<div>Online Members</div>
|
||||
{chatroomOnline}
|
||||
</div>
|
||||
<div className="bg-white rounded-lg m-2 shadow-2xl">
|
||||
<div>All Members</div>
|
||||
{chatroomUsers}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { Member } from "../datatypes"
|
||||
|
||||
import { database } from "../../../../firebase-config"
|
||||
import {ref, get, set} from "firebase/database"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
|
||||
export function Sidebar({user, chatRoomObj}) {
|
||||
const [profileData, setProfileData] = useState(null)
|
||||
// Active users list
|
||||
if (
|
||||
chatRoomObj.hasOwnProperty("users") &&
|
||||
chatRoomObj.users.hasOwnProperty("online")
|
||||
) {
|
||||
var activeUsers = [];
|
||||
var activeUsersJSON = chatRoomObj.users.online;
|
||||
for (var activeUser in activeUsersJSON)
|
||||
activeUsers.push(<Member memberObj={activeUsersJSON[activeUser]} />);
|
||||
var chatroomOnline = activeUsers
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
// Profile Information
|
||||
if (user.uid == chatRoomObj.UIDs[0]) {
|
||||
var profileUID = chatRoomObj.UIDs[1]
|
||||
} else {
|
||||
var profileUID = chatRoomObj.UIDs[0]
|
||||
}
|
||||
|
||||
get(ref(database, `users/${profileUID}`)).then((snapshot) => {
|
||||
var profileData = snapshot.val()
|
||||
setProfileData(profileData)
|
||||
})
|
||||
}
|
||||
}, [user])
|
||||
|
||||
return (
|
||||
<div>
|
||||
{profileData && (
|
||||
<div className="overflow-hidden h-dvh">
|
||||
<div className="m-2 h-[98%] grid grid-cols-1">
|
||||
<div className="flex place-content-center">
|
||||
<div className="bg-white rounded-lg m-2 shadow-2xl pt-10">
|
||||
<img src={profileData.pfp} className="w-[80%] relative mx-auto"/>
|
||||
<div className="font-bold text-[24px]">{profileData.firstName} {profileData.lastName}</div>
|
||||
@{profileData.username}
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg m-2 shadow-2xl">
|
||||
<div>In The Chat</div>
|
||||
{chatroomOnline}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
// System Imports
|
||||
import { Form, useForm } from "react-hook-form";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
// Dependency Imports
|
||||
import { Tab } from '@headlessui/react'
|
||||
|
||||
// Firebase Imports
|
||||
import { database } from "../../../../firebase-config";
|
||||
import { ref, set, get } from "firebase/database";
|
||||
|
||||
// Component Imports
|
||||
import { ChatRoomSidebar } from "../datatypes";
|
||||
|
||||
// Friend Imports (TEMP)
|
||||
import { Friend, FriendRequest } from "../friends/friends";
|
||||
|
||||
// DM Imports
|
||||
import { DM } from "../friends/dm";
|
||||
|
||||
/**
|
||||
* Create Room Component for /app Sidebar
|
||||
* @prop {JSON} loc - Location Object (latitude, longitude)
|
||||
* @returns {Object} - Create Room Component
|
||||
*/
|
||||
function CreateRoom({ loc }) {
|
||||
var { register, control, reset, handleSubmit } = useForm();
|
||||
|
||||
/**
|
||||
* Creates Room in Firebase DB
|
||||
* @prop {JSON} data - Room Data
|
||||
* @returns {void}
|
||||
*/
|
||||
function createRoom(data) {
|
||||
reset();
|
||||
var path =
|
||||
String(loc.latitude.toFixed(2)).replace(".", "") +
|
||||
"/" +
|
||||
String(loc.longitude.toFixed(2)).replace(".", "");
|
||||
var timestamp = new Date().getTime();
|
||||
var payload = {
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
timestamp: timestamp,
|
||||
latitude: loc.latitude,
|
||||
longitude: loc.longitude,
|
||||
path: path,
|
||||
};
|
||||
set(ref(database, `/rooms/${path}/${data.name}-${timestamp}`), payload);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="overflow-y-auto h-[90%]">
|
||||
<Form control={control} onSubmit={handleSubmit(createRoom)}>
|
||||
<input {...register("name")} placeholder="Room Name" className="mt-2" />
|
||||
<input
|
||||
{...register("description")}
|
||||
placeholder="Room Description"
|
||||
className="mt-2"
|
||||
/>
|
||||
<br />
|
||||
<div className="mt-3 mb-2">
|
||||
Creating room near ({loc.latitude.toFixed(2)},{" "}
|
||||
{loc.longitude.toFixed(2)})
|
||||
</div>
|
||||
<button className="p-2 cursor-pointer bg-cyan-500 text-white font-bold rounded-full mr-5">
|
||||
Create
|
||||
</button>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins class names together for Tailwind CSS
|
||||
* @param {...String} classes - Class names
|
||||
* @returns {String} - Class names (joined)
|
||||
*/
|
||||
function classNames(...classes) {
|
||||
return classes.filter(Boolean).join(' ')
|
||||
}
|
||||
|
||||
/**
|
||||
* App Page Sidebar Component
|
||||
* @prop {JSON} user - User Object
|
||||
* @prop {JSON} location - Location Object (latitude, longitude)
|
||||
* @prop {Boolean} loadingLoc - Loading Location State
|
||||
* @returns {Object} - App Page Sidebar Component
|
||||
*/
|
||||
export function Sidebar({user,location,loadingLoc}) {
|
||||
const [nearbyArr, setNearbyArr] = useState([])
|
||||
const [nearbyArrReady, setNearbyArrReady] = useState(false)
|
||||
const [friends, setFriends] = useState([])
|
||||
const [friendRequests, setFriendRequests] = useState(null)
|
||||
const [dms, setDMs] = useState(null)
|
||||
// Add myRooms to Sidebar
|
||||
var myRoomArr = [];
|
||||
for (var room in user.rooms) {
|
||||
get(ref(database, `/rooms/${user.rooms[room].path}/${user.rooms[room].name}-${user.rooms[room].timestamp}`)).then((snapshot) => {
|
||||
var newRoom = (
|
||||
<ChatRoomSidebar
|
||||
roomObj={snapshot.val()}
|
||||
key={snapshot.val().timestamp}
|
||||
/>
|
||||
);
|
||||
myRoomArr.push(newRoom);
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
var nearbyArr = []
|
||||
if (location) {
|
||||
var path = String(location.latitude.toFixed(2)).replace(".", "") + "/" + String(location.longitude.toFixed(2)).replace(".", "");
|
||||
get(ref(database, `/rooms/${path}`)).then((snapshot) => {
|
||||
// Add nearby to Sidebar
|
||||
if (snapshot.exists()) {
|
||||
var rooms = snapshot.val()
|
||||
for (var room in rooms) {
|
||||
var newRoom = (
|
||||
<ChatRoomSidebar
|
||||
roomObj={rooms[room]}
|
||||
key={rooms[room].timestamp}
|
||||
/>
|
||||
);
|
||||
nearbyArr.push(newRoom);
|
||||
}
|
||||
} else {
|
||||
nearbyArr.push(<div className="pt-5">No Nearby Rooms<br />Create One?</div>)
|
||||
}
|
||||
setNearbyArr(nearbyArr)
|
||||
setNearbyArrReady(true)
|
||||
})
|
||||
}
|
||||
}, [location])
|
||||
|
||||
useEffect(() => {
|
||||
if (user && user.friends) {
|
||||
get(ref(database, "/users/")).then((snapshot) => {
|
||||
var users = snapshot.val();
|
||||
var friends = [];
|
||||
for (var friend in user.friends.friends) {
|
||||
friends.push(<Friend user={user} friendObj={users[friend]} key={friend} />);
|
||||
}
|
||||
setFriends(friends);
|
||||
});
|
||||
|
||||
var requestArr = [];
|
||||
for (var request in user.friends.requests) {
|
||||
get(ref(database, `/users/${request}`)).then((snapshot) => {
|
||||
requestArr.push(<FriendRequest requestingUser={snapshot.val()} user={user} key={request} />);
|
||||
});
|
||||
}
|
||||
setFriendRequests(requestArr);
|
||||
} else {
|
||||
setFriends(<div>No Friends</div>);
|
||||
setFriendRequests(<div>No Friend Requests</div>);
|
||||
}
|
||||
|
||||
get(ref(database, `/dms`)).then((snapshot) => {
|
||||
var dmsList = snapshot.val();
|
||||
var dmArr = [];
|
||||
for(var dmRoom in dmsList) {
|
||||
if (user.uid == dmsList[dmRoom].UIDs[0]) {
|
||||
get(ref(database, `/users/${dmsList[dmRoom].UIDs[1]}`)).then((snapshot) => {
|
||||
var friendObj = snapshot.val()
|
||||
dmArr.push(<DM user={user} friendObj={friendObj} key={dmRoom}/>);
|
||||
})
|
||||
} else if (user.uid == dmsList[dmRoom].UIDs[1]) {
|
||||
get(ref(database, `/users/${dmsList[dmRoom].UIDs[0]}`)).then((snapshot) => {
|
||||
var friendObj = snapshot.val()
|
||||
dmArr.push(<DM user={user} friendObj={friendObj} key={dmRoom}/>);
|
||||
})
|
||||
}
|
||||
}
|
||||
if (dmArr.length == 0) {
|
||||
dmArr.push(<div>No DMs</div>);
|
||||
}
|
||||
setDMs(dmArr);
|
||||
})
|
||||
}, [user])
|
||||
|
||||
return (
|
||||
<div className="h-dvh bg-[aliceblue] pt-2 pb-2 pl-2 pr-1">
|
||||
<div className="bg-white rounded-lg h-[98%] mb-[10px] mt-[-18px] mr-2">
|
||||
<Tab.Group>
|
||||
<Tab.List className="bg-[#D3D3D3] rounded-lg mt-5">
|
||||
<Tab className={({ selected }) =>
|
||||
classNames(
|
||||
'w-[30%]',
|
||||
selected
|
||||
? 'bg-cyan-500 text-white font-bold shadow hover:bg-white/[0.6] hover:text-black'
|
||||
: 'hover:bg-cyan-500/[0.6] hover:text-white hover:font-bold'
|
||||
)} defaultIndex={1}>Nearby</Tab>
|
||||
<Tab className={({ selected }) =>
|
||||
classNames(
|
||||
'w-[30%]',
|
||||
selected
|
||||
? 'bg-cyan-500 text-white font-bold shadow hover:bg-white/[0.6] hover:text-black'
|
||||
: 'hover:bg-cyan-500/[0.6] hover:text-white hover:font-bold'
|
||||
)}>My Rooms</Tab>
|
||||
<Tab className={({ selected }) =>
|
||||
classNames(
|
||||
'w-[30%]',
|
||||
selected
|
||||
? 'bg-cyan-500 text-white font-bold shadow hover:bg-white/[0.6] hover:text-black'
|
||||
: 'hover:bg-cyan-500/[0.6] hover:text-white hover:font-bold'
|
||||
)}>Create</Tab>
|
||||
<Tab className={({ selected }) =>
|
||||
classNames(
|
||||
'w-[30%]',
|
||||
selected
|
||||
? 'bg-cyan-500 text-white font-bold shadow hover:bg-white/[0.6] hover:text-black'
|
||||
: 'hover:bg-cyan-500/[0.6] hover:text-white hover:font-bold'
|
||||
)} defaultIndex={1}>DMs</Tab>
|
||||
<Tab className={({ selected }) =>
|
||||
classNames(
|
||||
'w-[30%]',
|
||||
selected
|
||||
? 'bg-cyan-500 text-white font-bold shadow hover:bg-white/[0.6] hover:text-black'
|
||||
: 'hover:bg-cyan-500/[0.6] hover:text-white hover:font-bold'
|
||||
)} defaultIndex={1}>Friends</Tab>
|
||||
<Tab className={({ selected }) =>
|
||||
classNames(
|
||||
'w-[30%]',
|
||||
selected
|
||||
? 'bg-cyan-500 text-white font-bold shadow hover:bg-white/[0.6] hover:text-black'
|
||||
: 'hover:bg-cyan-500/[0.6] hover:text-white hover:font-bold'
|
||||
)} defaultIndex={1}>Requests</Tab>
|
||||
|
||||
</Tab.List>
|
||||
<Tab.Panels>
|
||||
<Tab.Panel>
|
||||
<div className="overflow-y-auto h-[90%]">
|
||||
<div>
|
||||
{loadingLoc && <div>Loading...</div>}
|
||||
{nearbyArrReady && nearbyArr}
|
||||
</div>
|
||||
</div>
|
||||
</Tab.Panel>
|
||||
<Tab.Panel>
|
||||
<div className="overflow-y-auto h-[90%]">
|
||||
<div>
|
||||
{!myRoomArr && <div>No User Saved Rooms</div>}
|
||||
{myRoomArr}
|
||||
</div>
|
||||
</div>
|
||||
</Tab.Panel>
|
||||
<Tab.Panel>
|
||||
{!loadingLoc && <CreateRoom loc={location} />}
|
||||
{loadingLoc && <div>Loading...</div>}
|
||||
</Tab.Panel>
|
||||
<Tab.Panel>
|
||||
{dms}
|
||||
</Tab.Panel>
|
||||
<Tab.Panel>
|
||||
{friends}
|
||||
</Tab.Panel>
|
||||
<Tab.Panel>
|
||||
{friendRequests}
|
||||
</Tab.Panel>
|
||||
</Tab.Panels>
|
||||
</Tab.Group>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,18 +1,8 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
backgroundImage: {
|
||||
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
|
||||
"gradient-conic":
|
||||
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"git": {
|
||||
"deploymentEnabled": {
|
||||
"gh-pages": false
|
||||
}
|
||||
}
|
||||
}
|
||||