From 38600326dffa126abcd1e43d6043aad1a1c39365 Mon Sep 17 00:00:00 2001 From: Simon Huang Date: Fri, 9 Oct 2020 20:43:35 -0400 Subject: [PATCH] changed About to Settings, added username change field --- package-lock.json | 10 ++ package.json | 2 + schema.ts | 2 +- src/components/About.tsx | 42 ------- src/components/Frame.css | 7 ++ src/components/Frame.tsx | 6 +- src/components/Messages.css | 1 + src/components/Messages.tsx | 14 ++- src/components/OnlineList.css | 7 -- src/components/OnlineList.tsx | 4 +- src/components/Player/ReactPlayerFrame.tsx | 8 +- src/components/Player/SubscriptionFrame.tsx | 4 +- src/components/RoomHeader.tsx | 2 +- src/components/{About.css => Settings.css} | 22 +++- src/components/Settings.tsx | 130 ++++++++++++++++++++ src/pages/Room.tsx | 2 +- 16 files changed, 192 insertions(+), 71 deletions(-) delete mode 100644 src/components/About.tsx rename src/components/{About.css => Settings.css} (53%) create mode 100644 src/components/Settings.tsx diff --git a/package-lock.json b/package-lock.json index d656dbb..6ea72ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1492,6 +1492,11 @@ "@hapi/hoek": "^8.3.0" } }, + "@hookform/error-message": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@hookform/error-message/-/error-message-0.0.4.tgz", + "integrity": "sha512-as5acnxkWuF5XrzHhZlzdXUk4zpHNzFLe+uGyaJtSrMbDVTvcNL62ESRK5DTVGPix8shJnt6G2LfrobHBPpvYg==" + }, "@iconify/icons-simple-icons": { "version": "1.0.47", "resolved": "https://registry.npmjs.org/@iconify/icons-simple-icons/-/icons-simple-icons-1.0.47.tgz", @@ -12032,6 +12037,11 @@ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz", "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==" }, + "react-hook-form": { + "version": "6.9.2", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-6.9.2.tgz", + "integrity": "sha512-vCPEbHVCRvsoqrQARgQ7a3VrXzqbFOO53gHFRdQzLzHMT9kxum3wfcSi8A1b49KPRsomvsqexH4tBUJMneEu+Q==" + }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", diff --git a/package.json b/package.json index af3f725..c044bd4 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "@capacitor/android": "^2.4.0", "@capacitor/core": "2.4.0", "@capacitor/ios": "^2.4.0", + "@hookform/error-message": "0.0.4", "@ionic/react": "^5.0.7", "@ionic/react-router": "^5.0.7", "@testing-library/jest-dom": "^4.2.4", @@ -24,6 +25,7 @@ "node-sass": "^4.14.1", "react": "^16.13.0", "react-dom": "^16.13.0", + "react-hook-form": "^6.9.2", "react-player": "^2.6.0", "react-router": "^5.1.2", "react-router-dom": "^5.1.2", diff --git a/schema.ts b/schema.ts index ed5c477..5fb1ab8 100644 --- a/schema.ts +++ b/schema.ts @@ -24,7 +24,7 @@ const collection = { requests: [ { createdAt: 'timestamp', - time: '01:25:44', // Relevant for 'play', 'pause' types + data: '01:25:44', // Contents of data depend on type of request type: 'updateState', senderId: 'userId', }, diff --git a/src/components/About.tsx b/src/components/About.tsx deleted file mode 100644 index 631bec6..0000000 --- a/src/components/About.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { IonCol, IonGrid, IonIcon, IonRow, IonRouterLink } from '@ionic/react'; -import { logoGithub } from 'ionicons/icons'; -import { Icon } from '@iconify/react'; -import discordIcon from '@iconify/icons-simple-icons/discord'; - -import React from 'react'; -import './About.css'; - -type AboutProps = { - pane: string; -}; - -const About: React.FC = ({ pane }) => { - return ( - - - - Any feedback, questions, or issues? - - 🐢🐢 - - - - - - - - - - - - - - - - - - - ); -}; - -export default About; diff --git a/src/components/Frame.css b/src/components/Frame.css index 5363e92..69e197f 100644 --- a/src/components/Frame.css +++ b/src/components/Frame.css @@ -19,3 +19,10 @@ ion-fab-button { --background: var(--ion-color-secondary-shade); --background-activated: var(--ion-color-secondary); } + +ion-list-header { + color: var(--ion-color-primary); + font-size: 20px; + border-bottom: 1px solid + var(--ion-item-border-color, var(--ion-border-color, var(--ion-color-step-150, rgba(0, 0, 0, 0.13)))); +} diff --git a/src/components/Frame.tsx b/src/components/Frame.tsx index 56b3850..649eb93 100644 --- a/src/components/Frame.tsx +++ b/src/components/Frame.tsx @@ -1,10 +1,10 @@ import { IonCard, IonIcon, IonSegment, IonSegmentButton } from '@ionic/react'; import { chatboxOutline, informationCircleOutline, peopleOutline } from 'ionicons/icons'; import React, { useState } from 'react'; -import About from './About'; import './Frame.css'; import Messages from './Messages'; import OnlineList from './OnlineList'; +import Settings from './Settings'; type FrameProps = { ownerId: string; @@ -26,7 +26,7 @@ const Frame: React.FC = ({ ownerId, roomId, userId, userList, joinTi setPane('online')}> - setPane('about')}> + setPane('settings')}> @@ -40,7 +40,7 @@ const Frame: React.FC = ({ ownerId, roomId, userId, userList, joinTi joinTime={joinTime} > - + ); }; diff --git a/src/components/Messages.css b/src/components/Messages.css index 5fb6211..5988a02 100644 --- a/src/components/Messages.css +++ b/src/components/Messages.css @@ -41,6 +41,7 @@ .message-toolbar { padding-left: 5px; --background: var(--ion-color-light); + border-top: 1px solid #777; } .footer-ios ion-toolbar:first-of-type { diff --git a/src/components/Messages.tsx b/src/components/Messages.tsx index 178f97d..1aa3b3f 100644 --- a/src/components/Messages.tsx +++ b/src/components/Messages.tsx @@ -68,11 +68,11 @@ const Messages: React.FC = ({ pane, ownerId, roomId, userId, user for (const req of requests) { if (req.createdAt > joinTime && req.type !== 'updateState') { arr.push({ - content: processType(req.type, req.time), + content: processType(req.type, req.data), createdAt: req.createdAt, id: req.senderId + req.createdAt, senderId: req.senderId, - type: 'system', + type: req.type, }); } } @@ -86,16 +86,18 @@ const Messages: React.FC = ({ pane, ownerId, roomId, userId, user }, [roomId, joinTime]); // Convert request type to message content - const processType = (type: string, time: number): string => { + const processType = (type: string, data: any): string => { switch (type) { case 'change': return 'changed the video.'; case 'join': return 'joined the room.'; case 'pause': - return 'paused the video at ' + secondsToTimestamp(time); + return 'paused the video at ' + secondsToTimestamp(data); case 'play': - return 'played the video from ' + secondsToTimestamp(time); + return 'played the video from ' + secondsToTimestamp(data); + case 'nameChange': + return data.prev + ' changed their name to ' + data.curr; default: return ''; } @@ -170,7 +172,7 @@ const Messages: React.FC = ({ pane, ownerId, roomId, userId, user return ( - {getName(msg.senderId) + ' ' + msg.content} + {msg.type === 'nameChange' ? msg.content : getName(msg.senderId) + ' ' + msg.content} ); diff --git a/src/components/OnlineList.css b/src/components/OnlineList.css index 1d57962..96ff642 100644 --- a/src/components/OnlineList.css +++ b/src/components/OnlineList.css @@ -2,13 +2,6 @@ height: calc(100% - 162px + 52px); } -.online-header { - border-bottom: 1px solid var(--ion-color-primary); - color: var(--ion-color-primary); - font-size: 20px; - align-items: center; -} - .online-list { padding-top: 0; padding-bottom: 0; diff --git a/src/components/OnlineList.tsx b/src/components/OnlineList.tsx index dcaef78..946d5c6 100644 --- a/src/components/OnlineList.tsx +++ b/src/components/OnlineList.tsx @@ -66,7 +66,7 @@ const OnlineList: React.FC = ({ pane, roomId, userId, userList return ( - Online + Online {Array.from(userList.values()).map((user) => { return ( @@ -78,7 +78,7 @@ const OnlineList: React.FC = ({ pane, roomId, userId, userList - Invite friends! + Invite friends! diff --git a/src/components/Player/ReactPlayerFrame.tsx b/src/components/Player/ReactPlayerFrame.tsx index 70a72c9..fcbd769 100644 --- a/src/components/Player/ReactPlayerFrame.tsx +++ b/src/components/Player/ReactPlayerFrame.tsx @@ -28,7 +28,7 @@ const ReactPlayerFrame: React.FC = ({ ownerId, userId, ro db.collection('rooms') .doc(roomId) .update({ - requests: arrayUnion({ createdAt: Date.now(), senderId: userId, time: currTime, type: 'play' }), + requests: arrayUnion({ createdAt: Date.now(), senderId: userId, data: currTime, type: 'play' }), }); } } @@ -48,7 +48,7 @@ const ReactPlayerFrame: React.FC = ({ ownerId, userId, ro db.collection('rooms') .doc(roomId) .update({ - requests: arrayUnion({ createdAt: Date.now(), senderId: userId, time: currTime, type: 'pause' }), + requests: arrayUnion({ createdAt: Date.now(), senderId: userId, data: currTime, type: 'pause' }), }); } } @@ -88,7 +88,7 @@ const ReactPlayerFrame: React.FC = ({ ownerId, userId, ro db.collection('rooms') .doc(roomId) .update({ - requests: arrayUnion({ createdAt: Date.now(), senderId: userId, time: 0, type: 'updateState' }), + requests: arrayUnion({ createdAt: Date.now(), senderId: userId, data: 0, type: 'updateState' }), }); } }; @@ -119,7 +119,7 @@ const ReactPlayerFrame: React.FC = ({ ownerId, userId, ro player.current?.seekTo(realTimeState); roomRef.update({ - requests: arrayUnion({ createdAt: Date.now(), senderId: userId, time: 0, type: 'updateState' }), + requests: arrayUnion({ createdAt: Date.now(), senderId: userId, data: 0, type: 'updateState' }), }); } } diff --git a/src/components/Player/SubscriptionFrame.tsx b/src/components/Player/SubscriptionFrame.tsx index 62156d0..eb8171a 100644 --- a/src/components/Player/SubscriptionFrame.tsx +++ b/src/components/Player/SubscriptionFrame.tsx @@ -58,7 +58,7 @@ const SubscriptionFrame: React.FC = ({ ownerId, userId, db.collection('rooms') .doc(roomId) .update({ - requests: arrayUnion({ createdAt: Date.now(), senderId: userId, time: e.data.time, type: type }), + requests: arrayUnion({ createdAt: Date.now(), senderId: userId, data: e.data.time, type: type }), }); } }; @@ -167,7 +167,7 @@ const SubscriptionFrame: React.FC = ({ ownerId, userId, seekTo(actual?.time); roomRef.update({ - requests: arrayUnion({ createdAt: Date.now(), senderId: userId, time: 0, type: 'updateState' }), + requests: arrayUnion({ createdAt: Date.now(), senderId: userId, data: 0, type: 'updateState' }), }); } } diff --git a/src/components/RoomHeader.tsx b/src/components/RoomHeader.tsx index 438f24d..76e8493 100644 --- a/src/components/RoomHeader.tsx +++ b/src/components/RoomHeader.tsx @@ -25,7 +25,7 @@ const RoomHeader: React.FC = ({ roomId, userId, ownerId }) => { .collection('rooms') .doc(roomId) .update({ - requests: arrayUnion({ createdAt: Date.now(), senderId: userId, time: 0, type: 'change' }), + requests: arrayUnion({ createdAt: Date.now(), senderId: userId, data: 0, type: 'change' }), }); setVideoUrl(''); diff --git a/src/components/About.css b/src/components/Settings.css similarity index 53% rename from src/components/About.css rename to src/components/Settings.css index 6145769..35b666f 100644 --- a/src/components/About.css +++ b/src/components/Settings.css @@ -1,6 +1,24 @@ +.settings-header { + text-align: left; +} + +.name-item { + font-size: 14px; + color: var(--ion-color-secondary); +} + +.name-input { + color: #fff; +} + +.error-message { + padding-top: 4px; + padding-left: 16px; + color: #e61a61; +} + .about-grid { - margin-top: 50px; - max-width: 500px; + margin-top: 20px; font-size: 20px; font-style: bold; color: var(--ion-color-secondary); diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx new file mode 100644 index 0000000..6cfd969 --- /dev/null +++ b/src/components/Settings.tsx @@ -0,0 +1,130 @@ +import { ErrorMessage } from '@hookform/error-message'; +import discordIcon from '@iconify/icons-simple-icons/discord'; +import { Icon } from '@iconify/react'; +import { + IonCol, + IonContent, + IonGrid, + IonIcon, + IonInput, + IonItem, + IonLabel, + IonListHeader, + IonRouterLink, + IonRow, + IonToast, +} from '@ionic/react'; +import { logoGithub } from 'ionicons/icons'; +import React, { useState } from 'react'; +import { Controller, useForm } from 'react-hook-form'; +import { arrayUnion, db, rtdb } from '../services/firebase'; +import './Settings.css'; + +type SettingsProps = { + pane: string; + roomId: string; + userId: string; +}; + +const Settings: React.FC = ({ pane, roomId, userId }) => { + const { control, errors, setValue, getValues } = useForm({ mode: 'onChange' }); + const [showNameChange, setShowNameChange] = useState(false); + + // Update databases with new username + const changeName = async () => { + const newName = getValues('username'); + if (newName !== '') { + const snapshot = await db.collection('users').doc(userId).get(); + const prevName = snapshot.data()?.name; + db.collection('users').doc(userId).update({ + name: newName, + }); + rtdb.ref('/rooms/' + roomId + '/' + userId).set({ name: newName }); + + // Send 'nameChange' request for all clients to get a message about the name change + db.collection('rooms') + .doc(roomId) + .update({ + requests: arrayUnion({ + createdAt: Date.now(), + senderId: userId, + data: { prev: prevName, curr: newName }, + type: 'nameChange', + }), + }); + + setShowNameChange(true); + } + }; + + const onEnter = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + if (!errors.username) { + changeName(); + setValue('username', ''); + } + } + }; + + return ( + + Settings + + Change Username + ( + onEnter(e)} + placeholder="New name" + maxlength={20} + value={value} + class="name-input" + /> + )} + control={control} + rules={{ + minLength: { value: 4, message: '⚠ Must be at least 4 characters long' }, + pattern: { value: /^\w+$/, message: '⚠ Must be alphanumeric' }, + }} + > + + + + + + Any feedback, questions, or issues? + + 🐢🐢 + + + + + + + + + + + + + + + + + + + setShowNameChange(false)} + position="top" + message="Username changed successfully" + > + + ); +}; + +export default Settings; diff --git a/src/pages/Room.tsx b/src/pages/Room.tsx index 89c1718..01e0719 100644 --- a/src/pages/Room.tsx +++ b/src/pages/Room.tsx @@ -64,7 +64,7 @@ const Room: React.FC> = ({ match }) => { // Keep track of online user presence in realtime database rooms roomRef.on('value', async (snapshot) => { - // Populate list of users in a room + // Populate list of usernames in a room const map: Map = new Map(); snapshot.forEach((childSnapshot) => { if (childSnapshot.key !== null && childSnapshot.key !== 'userCount') {