Skip to content

Commit affd4ad

Browse files
Dean SoferDean Sofer
authored andcommitted
Reorganized dialogues
1 parent 50c0ba5 commit affd4ad

File tree

10 files changed

+205
-312
lines changed

10 files changed

+205
-312
lines changed
File renamed without changes.

src/Board/Toolbar.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { useContext } from 'react';
2+
import Avatar from '../Avatar';
3+
import { DialogContext } from '../Dialogues';
4+
import type { SnapshotOrNullType } from '../Types';
5+
import './Toolbar.css';
6+
7+
interface ToolbarProps {
8+
friendData: SnapshotOrNullType;
9+
}
10+
11+
export default function Toolbar({ friendData }: ToolbarProps) {
12+
const { state: dialogState, toggle } = useContext(DialogContext)!;
13+
const friendDataValue = friendData?.val();
14+
15+
return (
16+
<div id="toolbar" onPointerUp={() => toggle('friends')}>
17+
{friendDataValue
18+
? <Avatar user={friendDataValue} />
19+
: <a className={`material-icons notranslate ${dialogState && 'active' || ''}`}>account_circle</a>}
20+
<h2>{friendDataValue?.name ?? 'Local'}</h2>
21+
</div>
22+
);
23+
}

src/Dialogues/DialogContext.tsx

Lines changed: 0 additions & 17 deletions
This file was deleted.

src/Dialogues/Friends.tsx

Lines changed: 54 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,57 @@
11
// Import FirebaseAuth and firebase.
2-
import { useState, useCallback, useRef, ReactNode, useEffect, useContext } from 'react'; // Added useContext
2+
import { useState, useCallback, useRef, ReactNode, useEffect, useContext } from 'react';
33
import type { ChangeEventHandler } from 'react';
44
import { formatDistance } from 'date-fns';
5-
import DialogContext, { DialogContextType } from './DialogContext'; // Added DialogContext
5+
import { DialogContext } from '.';
66
import firebase from 'firebase/compat/app';
77
import 'firebase/compat/auth';
88
import 'firebase/compat/database';
9-
import { UserData, Match } from '../Types';
9+
import { UserData, Match, SnapshotOrNullType } from '../Types';
1010
import Avatar from '../Avatar';
1111
import './Friends.css'
12-
import ToggleFullscreen from '../ToggleFullscreen';
12+
import ToggleFullscreen from './ToggleFullscreen';
13+
import { saveMessagingDeviceToken } from '../firebase-messaging-setup';
14+
1315
type Users = { [key: string]: UserData }
1416

15-
export default function Friends({ authUser, load, reset }) { // Removed toggle from props
16-
const context = useContext(DialogContext);
17-
if (!context) {
18-
console.error('DialogContext not found in Friends component');
19-
return null;
20-
}
21-
const { toggleDialog } = context; // Destructure toggleDialog from context
17+
type FriendsProps = {
18+
user: SnapshotOrNullType;
19+
load: (userId: string, key: string) => void;
20+
reset: () => void;
21+
}
22+
23+
export default function Friends({ user, load, reset }: FriendsProps) {
24+
const { toggle } = useContext(DialogContext)!;
2225

2326
const searchRef = useRef<HTMLInputElement>(null);
2427
const [users, setUsers] = useState<Users>({});
2528
const [isExpanded, setIsExpanded] = useState(false);
2629
const [matches, setMatches] = useState<firebase.database.DataSnapshot>([]);
2730
const [searchResults, setSearchResults] = useState<firebase.database.DataSnapshot>([]);
31+
const [hasAttemptedNotificationPermission, setHasAttemptedNotificationPermission] = useState(false);
32+
33+
// Request notification permission when Friends component mounts
34+
useEffect(() => {
35+
const requestPermission = async () => {
36+
if (user && Notification.permission === 'default' && !hasAttemptedNotificationPermission) {
37+
console.log("Friends modal opened, attempting notification permission request...");
38+
try {
39+
await saveMessagingDeviceToken();
40+
} catch (error) {
41+
console.error("Error requesting notification permission:", error);
42+
} finally {
43+
setHasAttemptedNotificationPermission(true);
44+
}
45+
}
46+
};
47+
requestPermission();
48+
}, [user, hasAttemptedNotificationPermission]);
2849

2950
// Synchronize Matches
3051
useEffect(() => {
31-
if (!authUser) return;
52+
if (!user) return;
3253

33-
const queryMatches = firebase.database().ref(`matches/${authUser.key}`).orderByChild('sort').limitToLast(100);
54+
const queryMatches = firebase.database().ref(`matches/${user.key}`).orderByChild('sort').limitToLast(100);
3455
const subscriber = (snapshot: firebase.database.DataSnapshot) => {
3556
setMatches(snapshot);
3657
snapshot.forEach(match => {
@@ -47,31 +68,38 @@ export default function Friends({ authUser, load, reset }) { // Removed toggle f
4768
return () => {
4869
queryMatches.off('value', subscriber);
4970
}
50-
}, [authUser]);
71+
}, [user]);
5172

5273
const onSearch: ChangeEventHandler<HTMLInputElement> = useCallback(async () => {
5374
if (searchRef.current?.value) {
5475
const search = searchRef.current.value
5576
const searchSnapshot = await firebase.database().ref('users').orderByChild('search').startAt(search.toLocaleLowerCase()).get();
56-
// const results: UserData[] = []
57-
// searchSnapshot.forEach(result => {
58-
// results.push(result.val())
59-
// })
6077
setSearchResults(searchSnapshot)
6178
} else {
6279
setSearchResults([])
6380
}
6481
}, []);
6582

66-
if (!authUser) return null;
83+
if (!user) return null;
6784

6885
const renderFriends: ReactNode[] = []
6986
const friends: string[] = []
7087

7188
const NOW = new Date()
7289

90+
const handleLoad = useCallback((userId: string) => {
91+
if (!user?.key) return;
92+
load(userId, user.key);
93+
toggle(false);
94+
}, [load, user?.key, toggle]);
95+
96+
const handleReset = useCallback(() => {
97+
reset();
98+
toggle(false);
99+
}, [reset, toggle]);
100+
73101
const row = (user: UserData, match?: Match) =>
74-
<li key={user.uid} onPointerUp={() => { load(user.uid, authUser.key); toggleDialog(false); }}> {/* Use toggleDialog(false) */}
102+
<li key={user.uid} onPointerUp={() => handleLoad(user.uid)}>
75103
<Avatar user={user} />
76104
<div>
77105
<h3>{user.name}</h3>
@@ -95,15 +123,15 @@ export default function Friends({ authUser, load, reset }) { // Removed toggle f
95123
})
96124
searchResults.forEach(result => {
97125
const resultData: UserData = result.val()
98-
if (result.key === authUser.key || friends.includes(result.key) || searchReject(resultData)) {
126+
if (result.key === user.key || friends.includes(result.key) || searchReject(resultData)) {
99127
return;
100128
}
101129
renderFriends.push(row(resultData))
102130
})
103131

104132
const invite = () => {
105-
if (authUser.key) {
106-
const shareUrl = (new URL(authUser.key, location.href)).toString()
133+
if (user.key) {
134+
const shareUrl = (new URL(user.key, location.href)).toString()
107135
navigator.clipboard?.writeText?.(shareUrl)
108136
navigator.share?.({
109137
url: shareUrl,
@@ -135,13 +163,13 @@ export default function Friends({ authUser, load, reset }) { // Removed toggle f
135163
</li>
136164
: null}
137165
<li>
138-
<a onPointerUp={() => toggleDialog('profile')}> {/* Use toggleDialog('profile') */}
166+
<a onPointerUp={() => toggle('profile')}>
139167
<span className="material-icons notranslate">manage_accounts</span>
140168
Edit Profile
141169
</a>
142170
</li>
143171
<li>
144-
<a onPointerUp={reset}>
172+
<a onPointerUp={handleReset}>
145173
<span className="material-icons notranslate">restart_alt</span>
146174
Reset Match
147175
</a>
@@ -167,7 +195,7 @@ export default function Friends({ authUser, load, reset }) { // Removed toggle f
167195
</menu>
168196
<h1>
169197
<span>
170-
<span>{authUser.val().name}'s</span>
198+
<span>{user.val().name}'s</span>
171199
Matches
172200
</span>
173201
</h1>

src/Dialogues/Login.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import 'firebaseui/dist/firebaseui.css';
66
import * as firebaseui from 'firebaseui';
77
import firebase from 'firebase/compat/app';
88
import 'firebase/compat/auth';
9-
import ToggleFullscreen from '../ToggleFullscreen';
9+
import ToggleFullscreen from './ToggleFullscreen';
1010
import './Login.css';
1111

1212
// Configure FirebaseUI.

src/Dialogues/Profile.tsx

Lines changed: 57 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
// Import FirebaseAuth and firebase.
2-
import React, { useState, useCallback, ChangeEvent, useEffect, useContext } from 'react'; // Added React and useContext
2+
import React, { useState, useCallback, useContext } from 'react'; // Added React and useContext
33
import firebase from 'firebase/compat/app';
44
import 'firebase/compat/auth';
5-
import DialogContext, { DialogContextType } from './DialogContext'; // Added DialogContext
5+
import { DialogContext } from '.'; // Added DialogContext
66
import 'firebase/compat/database';
7-
import Avatar from '../Avatar';
8-
import type { UserData } from '../Types';
97
import './Profile.css'
10-
import { saveMessagingDeviceToken } from '../firebase-messaging-setup';
8+
import { SnapshotOrNullType } from '../Types';
119

1210
export const LANGUAGES = ["af", "af-NA", "af-ZA", "agq", "agq-CM", "ak", "ak-GH", "am",
1311
"am-ET", "ar", "ar-001", "ar-AE", "ar-BH", "ar-DJ", "ar-DZ",
@@ -110,80 +108,66 @@ export const LANGUAGES = ["af", "af-NA", "af-ZA", "agq", "agq-CM", "ak", "ak-GH"
110108
"zh-Hans-MO", "zh-Hans-SG", "zh-Hant", "zh-Hant-HK", "zh-Hant-MO",
111109
"zh-Hant-TW", "zu", "zu-ZA"];
112110

111+
type ProfileProps = {
112+
user: SnapshotOrNullType
113+
}
114+
export default function Profile({ user }: ProfileProps) {
115+
const { toggle } = useContext(DialogContext)!;
113116

114-
export default function Profile({ authUser }) { // Removed toggle from props
115-
const context = useContext(DialogContext);
116-
if (!context) {
117-
console.error('DialogContext not found in Profile component');
118-
return null;
119-
}
120-
const { toggleDialog } = context; // Destructure toggleDialog from context
121-
122-
const [editing, setEditing] = useState<UserData>(authUser?.val() || { uid: '', name: '', language: '', photoURL: '' });
123-
const [currentNotificationPermission, setCurrentNotificationPermission] = useState(Notification.permission);
124-
125-
const save = useCallback(async (event: React.FormEvent<HTMLFormElement>) => {
126-
event.preventDefault();
127-
if (!editing) return;
128-
const userRef = firebase.database().ref(`users/${authUser!.key}`);
129-
userRef.set(editing);
130-
console.log('Saved', editing);
131-
toggleDialog('friends'); // Use toggleDialog
132-
}, [editing, authUser, toggleDialog]); // Added toggleDialog to dependencies
117+
const [isLoading, setIsLoading] = useState(false);
118+
const [error, setError] = useState<string | null>(null);
119+
const [name, setName] = useState(user?.val()?.name || '');
133120

134-
const generateOnChange = (key: string) => (event: ChangeEvent<HTMLInputElement>) => {
135-
setEditing(editing => ({ ...editing, [key]: event.target.value }));
136-
};
121+
const handleNameChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
122+
setName(e.target.value);
123+
}, []);
137124

138-
const handleEnableNotificationsClick = useCallback(async () => {
125+
const handleSave = useCallback(async () => {
126+
if (!user?.key) return;
127+
128+
setIsLoading(true);
129+
setError(null);
139130
try {
140-
await saveMessagingDeviceToken();
141-
} catch (error) {
142-
console.error("Error requesting notification permission from profile:", error);
131+
const userRef = firebase.database().ref(`users/${user.key}`);
132+
await userRef.update({
133+
name: name,
134+
search: name.toLowerCase()
135+
});
136+
toggle(false);
137+
} catch (err) {
138+
setError(err instanceof Error ? err.message : 'An error occurred while saving profile');
139+
} finally {
140+
setIsLoading(false);
143141
}
144-
setCurrentNotificationPermission(Notification.permission);
145-
}, []);
142+
}, [user?.key, name, toggle]);
143+
144+
if (!user) return null;
146145

147-
return <section id="profile">
148-
<form onSubmit={save}>
146+
return (
147+
<section id="profile">
149148
<header>
150-
<h1>
151-
<a onPointerUp={() => toggleDialog('friends')}> {/* Use toggleDialog */}
152-
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="icon icon-tabler icons-tabler-outline icon-tabler-x"><path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M18 6l-12 12" /><path d="M6 6l12 12" /></svg>
153-
</a>
154-
Edit Profile
155-
<button type="submit">Save</button>
156-
</h1>
149+
<h1>Edit Profile</h1>
157150
</header>
158-
<label>
159-
Name
160-
<input type="text" name="name" value={editing.name} onChange={generateOnChange('name')} placeholder="Name" />
161-
</label>
162-
<label>
163-
Language
164-
<select name="language" value={editing.language} onChange={generateOnChange('language')} placeholder="Language">
165-
{LANGUAGES.map(language => (
166-
<option key={language}>{language}</option>
167-
))}
168-
</select>
169-
</label>
170-
<label>
171-
Avatar URL
172-
<input type="text" name="photoURL" value={editing.photoURL || ''} onChange={generateOnChange('photoURL')} placeholder="Photo URL" />
173-
</label>
174-
<Avatar user={editing} />
175-
176-
{(currentNotificationPermission === 'default' || currentNotificationPermission === 'denied') && (
177-
<>
178-
<p>Enable notifications to stay updated on game events.</p>
179-
<button type="button" onClick={handleEnableNotificationsClick}>
180-
Enable Notifications
181-
</button>
182-
</>
183-
)}
184-
{currentNotificationPermission === 'granted' && (
185-
<p>Notifications are enabled.</p>
186-
)}
187-
</form>
188-
</section>
151+
<div className="content">
152+
{error && <div className="error">{error}</div>}
153+
<div className="form-group">
154+
<label htmlFor="name">Name</label>
155+
<input
156+
id="name"
157+
type="text"
158+
value={name}
159+
onChange={handleNameChange}
160+
disabled={isLoading}
161+
/>
162+
</div>
163+
<button
164+
className="save-profile"
165+
onClick={handleSave}
166+
disabled={isLoading}
167+
>
168+
{isLoading ? 'Saving...' : 'Save Profile'}
169+
</button>
170+
</div>
171+
</section>
172+
);
189173
}

0 commit comments

Comments
 (0)