adminpage
This commit is contained in:
32
package-lock.json
generated
32
package-lock.json
generated
@@ -14,7 +14,8 @@
|
|||||||
"partykit": "^0.0.115",
|
"partykit": "^0.0.115",
|
||||||
"partysocket": "^1.1.4",
|
"partysocket": "^1.1.4",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1"
|
"react-dom": "^18.3.1",
|
||||||
|
"wouter": "^3.7.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.9.1",
|
"@eslint/js": "^9.9.1",
|
||||||
@@ -3314,6 +3315,12 @@
|
|||||||
"node": ">=16 || 14 >=14.17"
|
"node": ">=16 || 14 >=14.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mitt": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/mlly": {
|
"node_modules/mlly": {
|
||||||
"version": "1.7.4",
|
"version": "1.7.4",
|
||||||
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz",
|
||||||
@@ -3910,6 +3917,15 @@
|
|||||||
"node": ">=8.10.0"
|
"node": ">=8.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/regexparam": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/regexparam/-/regexparam-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-RSYAtP31mvYLkAHrOlh25pCNQ5hWnT106VukGaaFfuJrZFkGRX5GhUAdPqpSDXxOhA2c4akmRuplv1mRqnBn6Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/resolve": {
|
"node_modules/resolve": {
|
||||||
"version": "1.22.8",
|
"version": "1.22.8",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||||
@@ -4649,6 +4665,20 @@
|
|||||||
"@cloudflare/workerd-windows-64": "1.20240718.0"
|
"@cloudflare/workerd-windows-64": "1.20240718.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/wouter": {
|
||||||
|
"version": "3.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/wouter/-/wouter-3.7.1.tgz",
|
||||||
|
"integrity": "sha512-od5LGmndSUzntZkE2R5CHhoiJ7YMuTIbiXsa0Anytc2RATekgv4sfWRAxLEULBrp7ADzinWQw8g470lkT8+fOw==",
|
||||||
|
"license": "Unlicense",
|
||||||
|
"dependencies": {
|
||||||
|
"mitt": "^3.0.1",
|
||||||
|
"regexparam": "^3.0.0",
|
||||||
|
"use-sync-external-store": "^1.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/wrap-ansi": {
|
"node_modules/wrap-ansi": {
|
||||||
"version": "8.1.0",
|
"version": "8.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
|
||||||
|
|||||||
@@ -16,7 +16,8 @@
|
|||||||
"partykit": "^0.0.115",
|
"partykit": "^0.0.115",
|
||||||
"partysocket": "^1.1.4",
|
"partysocket": "^1.1.4",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1"
|
"react-dom": "^18.3.1",
|
||||||
|
"wouter": "^3.7.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.9.1",
|
"@eslint/js": "^9.9.1",
|
||||||
|
|||||||
@@ -33,11 +33,17 @@ interface ApplyMultiplierBonusMessage extends AuthenticatedMessage { // New mess
|
|||||||
multiplierBonus: number;
|
multiplierBonus: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface AdminBroadcastMessage extends AuthenticatedMessage {
|
||||||
|
type: 'admin-broadcast';
|
||||||
|
message: string;
|
||||||
|
targetUserId?: string; // Optional: if broadcasting to a specific user
|
||||||
|
}
|
||||||
|
|
||||||
interface UserJoinMessage extends AuthenticatedMessage {
|
interface UserJoinMessage extends AuthenticatedMessage {
|
||||||
type: 'user-join';
|
type: 'user-join';
|
||||||
}
|
}
|
||||||
|
|
||||||
type Message = ClickMessage | PurchaseUpgradeMessage | ApplyMultiplierBonusMessage | UserJoinMessage; // Updated Message type
|
type Message = ClickMessage | PurchaseUpgradeMessage | ApplyMultiplierBonusMessage | UserJoinMessage | AdminBroadcastMessage; // Updated Message type
|
||||||
|
|
||||||
const UPGRADES = {
|
const UPGRADES = {
|
||||||
clickMultiplier: { baseCost: 10, multiplier: 1.5, clickBonus: 1 },
|
clickMultiplier: { baseCost: 10, multiplier: 1.5, clickBonus: 1 },
|
||||||
@@ -59,6 +65,7 @@ const MILESTONES = [
|
|||||||
|
|
||||||
export default class GameServer implements Party.Server {
|
export default class GameServer implements Party.Server {
|
||||||
clerkClient: ReturnType<typeof createClerkClient>;
|
clerkClient: ReturnType<typeof createClerkClient>;
|
||||||
|
private userConnections: Map<string, Party.Connection> = new Map(); // Map userId to connection
|
||||||
|
|
||||||
constructor(readonly party: Party.Party) {
|
constructor(readonly party: Party.Party) {
|
||||||
this.clerkClient = createClerkClient({
|
this.clerkClient = createClerkClient({
|
||||||
@@ -86,6 +93,16 @@ export default class GameServer implements Party.Server {
|
|||||||
conn.send(JSON.stringify({ type: 'game-state', state: this.gameState }));
|
conn.send(JSON.stringify({ type: 'game-state', state: this.gameState }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onClose(conn: Party.Connection) {
|
||||||
|
// Remove the connection from the map when it closes
|
||||||
|
for (const [userId, connection] of this.userConnections.entries()) {
|
||||||
|
if (connection === conn) {
|
||||||
|
this.userConnections.delete(userId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async onMessage(message: string, sender: Party.Connection) {
|
async onMessage(message: string, sender: Party.Connection) {
|
||||||
const data = JSON.parse(message) as Message;
|
const data = JSON.parse(message) as Message;
|
||||||
|
|
||||||
@@ -145,6 +162,8 @@ export default class GameServer implements Party.Server {
|
|||||||
if (this.gameState.users[currentUserId].bonusMultiplier === undefined) {
|
if (this.gameState.users[currentUserId].bonusMultiplier === undefined) {
|
||||||
this.gameState.users[currentUserId].bonusMultiplier = 1;
|
this.gameState.users[currentUserId].bonusMultiplier = 1;
|
||||||
}
|
}
|
||||||
|
// Store the connection for targeted broadcasts
|
||||||
|
this.userConnections.set(currentUserId, sender);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
@@ -173,6 +192,13 @@ export default class GameServer implements Party.Server {
|
|||||||
console.warn(`Unauthenticated apply-multiplier-bonus from ${currentUserId} ignored.`);
|
console.warn(`Unauthenticated apply-multiplier-bonus from ${currentUserId} ignored.`);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'admin-broadcast':
|
||||||
|
if (isAuthenticated && currentUserId === this.party.env.CLERK_ADMIN_USERID) {
|
||||||
|
this.handleAdminBroadcast(data);
|
||||||
|
} else {
|
||||||
|
console.warn(`Unauthorized admin broadcast attempt from ${currentUserId}.`);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.broadcast();
|
this.broadcast();
|
||||||
@@ -209,6 +235,29 @@ export default class GameServer implements Party.Server {
|
|||||||
this.checkMilestones();
|
this.checkMilestones();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleAdminBroadcast(data: AdminBroadcastMessage) {
|
||||||
|
const broadcastPayload = {
|
||||||
|
type: 'admin-message',
|
||||||
|
message: data.message,
|
||||||
|
sender: 'Admin'
|
||||||
|
};
|
||||||
|
|
||||||
|
if (data.targetUserId) {
|
||||||
|
// Send to a specific user using the stored connection
|
||||||
|
const targetConn = this.userConnections.get(data.targetUserId);
|
||||||
|
if (targetConn) {
|
||||||
|
targetConn.send(JSON.stringify(broadcastPayload));
|
||||||
|
console.log(`Admin broadcasted to user ${data.targetUserId}: ${data.message}`);
|
||||||
|
} else {
|
||||||
|
console.warn(`Target user ${data.targetUserId} not found or not connected.`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Broadcast to all connections
|
||||||
|
this.party.broadcast(JSON.stringify(broadcastPayload));
|
||||||
|
console.log(`Admin broadcasted to all users: ${data.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleApplyMultiplierBonus(data: ApplyMultiplierBonusMessage, authenticatedUserId: string) {
|
handleApplyMultiplierBonus(data: ApplyMultiplierBonusMessage, authenticatedUserId: string) {
|
||||||
if (!this.gameState.users[authenticatedUserId]) {
|
if (!this.gameState.users[authenticatedUserId]) {
|
||||||
console.warn(`User ${authenticatedUserId} not found for multiplier bonus application.`);
|
console.warn(`User ${authenticatedUserId} not found for multiplier bonus application.`);
|
||||||
|
|||||||
54
src/App.tsx
54
src/App.tsx
@@ -12,14 +12,22 @@ import { useAuth } from '@clerk/clerk-react';
|
|||||||
import { ClickableMascot } from './components/ClickableMascot'; // Import ClickableMascot component
|
import { ClickableMascot } from './components/ClickableMascot'; // Import ClickableMascot component
|
||||||
import { ClickableMascot as ClickableMascotType, Upgrade } from './types'; // Import ClickableMascotType and Upgrade
|
import { ClickableMascot as ClickableMascotType, Upgrade } from './types'; // Import ClickableMascotType and Upgrade
|
||||||
import { UPGRADES } from './config/upgrades'; // Import UPGRADES for upgrade config
|
import { UPGRADES } from './config/upgrades'; // Import UPGRADES for upgrade config
|
||||||
|
import { useLocation, Link } from 'wouter'; // Import wouter hooks
|
||||||
|
import AdminPage from './components/AdminPage'; // Import AdminPage
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const { isSignedIn, isLoaded } = useAuth();
|
const { isSignedIn, isLoaded, userId: clerkUserId } = useAuth(); // Get clerkUserId from useAuth
|
||||||
const { gameState, sendClick, purchaseUpgrade, userId, sendMascotClickBonus } = usePartyKit(); // Renamed sendShoominionClickBonus
|
const { gameState, sendClick, purchaseUpgrade, userId, sendMascotClickBonus, lastMessage } = usePartyKit(); // Renamed sendShoominionClickBonus
|
||||||
const [celebrationMessage, setCelebrationMessage] = useState<string | null>(null);
|
const [celebrationMessage, setCelebrationMessage] = useState<string | null>(null);
|
||||||
const [previousMilestones, setPreviousMilestones] = useState<Record<string, boolean>>({});
|
const [previousMilestones, setPreviousMilestones] = useState<Record<string, boolean>>({});
|
||||||
const [mascotEntities, setMascotEntities] = useState<ClickableMascotType[]>([]);
|
const [mascotEntities, setMascotEntities] = useState<ClickableMascotType[]>([]);
|
||||||
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
const [location] = useLocation(); // Get current location from wouter
|
||||||
|
const [adminBroadcastMessage, setAdminBroadcastMessage] = useState<string | null>(null); // New state for admin messages
|
||||||
|
|
||||||
|
// Admin user ID from .env
|
||||||
|
const CLERK_ADMIN_USERID = import.meta.env.VITE_CLERK_ADMIN_USERID;
|
||||||
|
const isAdmin = clerkUserId === CLERK_ADMIN_USERID;
|
||||||
|
|
||||||
// Effect for milestone celebrations
|
// Effect for milestone celebrations
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -42,6 +50,21 @@ function App() {
|
|||||||
}
|
}
|
||||||
}, [gameState, previousMilestones]);
|
}, [gameState, previousMilestones]);
|
||||||
|
|
||||||
|
// Effect for receiving admin broadcast messages
|
||||||
|
useEffect(() => {
|
||||||
|
if (lastMessage) {
|
||||||
|
try {
|
||||||
|
const parsedMessage = JSON.parse(lastMessage);
|
||||||
|
if (parsedMessage.type === 'admin-message') {
|
||||||
|
setAdminBroadcastMessage(parsedMessage.message);
|
||||||
|
setTimeout(() => setAdminBroadcastMessage(null), 7000); // Message disappears after 7 seconds
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to parse last message in App.tsx:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [lastMessage]);
|
||||||
|
|
||||||
// Effect for spawning mascots
|
// Effect for spawning mascots
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!gameState) return;
|
if (!gameState) return;
|
||||||
@@ -152,12 +175,24 @@ function App() {
|
|||||||
const userBonusMultiplier = isSignedIn && userId ? gameState.users[userId]?.bonusMultiplier || 1 : 1;
|
const userBonusMultiplier = isSignedIn && userId ? gameState.users[userId]?.bonusMultiplier || 1 : 1;
|
||||||
const effectiveClickMultiplier = gameState.clickMultiplier * userBonusMultiplier;
|
const effectiveClickMultiplier = gameState.clickMultiplier * userBonusMultiplier;
|
||||||
|
|
||||||
|
// Render the AdminPage if the current location is /admin
|
||||||
|
if (location === '/admin') {
|
||||||
|
return <AdminPage />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen relative overflow-x-hidden">
|
<div className="min-h-screen relative overflow-x-hidden">
|
||||||
<Background background={gameState.currentBackground} />
|
<Background background={gameState.currentBackground} />
|
||||||
|
|
||||||
{/* User Button */}
|
{/* User Button and Admin Link */}
|
||||||
<div className="absolute top-4 right-4 z-50">
|
<div className="absolute top-4 right-4 z-50 flex items-center space-x-4">
|
||||||
|
{isAdmin && (
|
||||||
|
<Link href="/admin">
|
||||||
|
<a className="text-white bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded-md font-bold transition duration-300 ease-in-out">
|
||||||
|
Admin Page
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
<SignedIn>
|
<SignedIn>
|
||||||
<UserButton />
|
<UserButton />
|
||||||
</SignedIn>
|
</SignedIn>
|
||||||
@@ -172,6 +207,15 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Admin Broadcast Message */}
|
||||||
|
{adminBroadcastMessage && (
|
||||||
|
<div className="fixed top-20 left-0 right-0 z-50 flex justify-center pt-8">
|
||||||
|
<div className="bg-red-600 text-white px-8 py-4 rounded-full text-2xl font-bold shadow-2xl animate-pulse border-4 border-white">
|
||||||
|
📢 Admin Message: {adminBroadcastMessage} 📢
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="container mx-auto px-4 py-8 relative z-10">
|
<div className="container mx-auto px-4 py-8 relative z-10">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
|
|||||||
133
src/components/AdminPage.tsx
Normal file
133
src/components/AdminPage.tsx
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useUser } from '@clerk/clerk-react';
|
||||||
|
import { usePartyKit } from '../hooks/usePartyKit';
|
||||||
|
|
||||||
|
interface AdminMessage {
|
||||||
|
type: 'admin-message';
|
||||||
|
message: string;
|
||||||
|
sender: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AdminPage: React.FC = () => {
|
||||||
|
const { user } = useUser();
|
||||||
|
const { sendMessage, lastMessage, getToken } = usePartyKit();
|
||||||
|
const [broadcastMessage, setBroadcastMessage] = useState('');
|
||||||
|
const [targetUserId, setTargetUserId] = useState('');
|
||||||
|
const [receivedMessages, setReceivedMessages] = useState<AdminMessage[]>([]);
|
||||||
|
const [isAdmin, setIsAdmin] = useState(false);
|
||||||
|
|
||||||
|
// Replace with your actual admin user ID from .env or similar
|
||||||
|
const CLERK_ADMIN_USERID = import.meta.env.VITE_CLERK_ADMIN_USERID;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (user?.id === CLERK_ADMIN_USERID) {
|
||||||
|
setIsAdmin(true);
|
||||||
|
} else {
|
||||||
|
setIsAdmin(false);
|
||||||
|
}
|
||||||
|
}, [user, CLERK_ADMIN_USERID]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (lastMessage) {
|
||||||
|
try {
|
||||||
|
const parsedMessage = JSON.parse(lastMessage);
|
||||||
|
if (parsedMessage.type === 'admin-message') {
|
||||||
|
setReceivedMessages((prev) => [...prev, parsedMessage]);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to parse last message:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [lastMessage]);
|
||||||
|
|
||||||
|
const handleBroadcast = async () => {
|
||||||
|
if (!user || !isAdmin) {
|
||||||
|
alert('You are not authorized to send admin messages.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (broadcastMessage.trim()) {
|
||||||
|
const token = await getToken(); // Get the token using the exposed getToken from usePartyKit
|
||||||
|
const messagePayload = {
|
||||||
|
type: 'admin-broadcast',
|
||||||
|
token: token, // Send the Clerk token
|
||||||
|
userId: user.id,
|
||||||
|
userName: user.fullName || user.id,
|
||||||
|
message: broadcastMessage.trim(),
|
||||||
|
...(targetUserId.trim() && { targetUserId: targetUserId.trim() }),
|
||||||
|
};
|
||||||
|
sendMessage(JSON.stringify(messagePayload));
|
||||||
|
setBroadcastMessage('');
|
||||||
|
setTargetUserId('');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!isAdmin) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center min-h-screen bg-gray-900 text-white">
|
||||||
|
<p className="text-xl">Access Denied: You are not an administrator.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-900 text-white p-8">
|
||||||
|
<h1 className="text-3xl font-bold mb-6">Admin Broadcast Page</h1>
|
||||||
|
|
||||||
|
<div className="mb-8 p-6 bg-gray-800 rounded-lg shadow-lg">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">Send Broadcast Message</h2>
|
||||||
|
<div className="mb-4">
|
||||||
|
<label htmlFor="message" className="block text-lg font-medium mb-2">
|
||||||
|
Message:
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
id="message"
|
||||||
|
className="w-full p-3 rounded-md bg-gray-700 border border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
rows={4}
|
||||||
|
value={broadcastMessage}
|
||||||
|
onChange={(e) => setBroadcastMessage(e.target.value)}
|
||||||
|
placeholder="Enter your broadcast message here..."
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
<div className="mb-4">
|
||||||
|
<label htmlFor="targetUserId" className="block text-lg font-medium mb-2">
|
||||||
|
Target User ID (Optional, leave blank for all users):
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="targetUserId"
|
||||||
|
className="w-full p-3 rounded-md bg-gray-700 border border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
value={targetUserId}
|
||||||
|
onChange={(e) => setTargetUserId(e.target.value)}
|
||||||
|
placeholder="e.g., user_123abc"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={handleBroadcast}
|
||||||
|
className="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-6 rounded-md transition duration-300 ease-in-out"
|
||||||
|
>
|
||||||
|
Send Broadcast
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-6 bg-gray-800 rounded-lg shadow-lg">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">Received Admin Messages</h2>
|
||||||
|
<div className="max-h-64 overflow-y-auto bg-gray-700 p-4 rounded-md">
|
||||||
|
{receivedMessages.length === 0 ? (
|
||||||
|
<p className="text-gray-400">No admin messages received yet.</p>
|
||||||
|
) : (
|
||||||
|
<ul>
|
||||||
|
{receivedMessages.map((msg, index) => (
|
||||||
|
<li key={index} className="mb-2 p-3 bg-gray-600 rounded-md">
|
||||||
|
<strong>{msg.sender}:</strong> {msg.message}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AdminPage;
|
||||||
@@ -10,6 +10,7 @@ export function usePartyKit() {
|
|||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
const [gameState, setGameState] = useState<GameState | null>(null);
|
const [gameState, setGameState] = useState<GameState | null>(null);
|
||||||
const [socket, setSocket] = useState<PartySocket | null>(null);
|
const [socket, setSocket] = useState<PartySocket | null>(null);
|
||||||
|
const [lastMessage, setLastMessage] = useState<string | null>(null); // New state for last message
|
||||||
// Generate a persistent guest ID if the user is not signed in
|
// Generate a persistent guest ID if the user is not signed in
|
||||||
const [guestId] = useState(() => {
|
const [guestId] = useState(() => {
|
||||||
let storedGuestId = localStorage.getItem('bozo_guest_id');
|
let storedGuestId = localStorage.getItem('bozo_guest_id');
|
||||||
@@ -59,6 +60,7 @@ export function usePartyKit() {
|
|||||||
if (data.type === 'game-state') {
|
if (data.type === 'game-state') {
|
||||||
setGameState(data.state);
|
setGameState(data.state);
|
||||||
}
|
}
|
||||||
|
setLastMessage(event.data); // Store the raw last message
|
||||||
};
|
};
|
||||||
|
|
||||||
setSocket(ws);
|
setSocket(ws);
|
||||||
@@ -105,6 +107,14 @@ export function usePartyKit() {
|
|||||||
}
|
}
|
||||||
}, [socket, isSignedIn, user, getToken]);
|
}, [socket, isSignedIn, user, getToken]);
|
||||||
|
|
||||||
|
// Determine the userId and userName to expose based on sign-in status
|
||||||
|
// Only expose Clerk user ID/name if signed in, otherwise undefined
|
||||||
|
const sendMessage = useCallback((message: string) => {
|
||||||
|
if (socket) {
|
||||||
|
socket.send(message);
|
||||||
|
}
|
||||||
|
}, [socket]);
|
||||||
|
|
||||||
// Determine the userId and userName to expose based on sign-in status
|
// Determine the userId and userName to expose based on sign-in status
|
||||||
// Only expose Clerk user ID/name if signed in, otherwise undefined
|
// Only expose Clerk user ID/name if signed in, otherwise undefined
|
||||||
const currentUserId = isSignedIn && user ? user.id : undefined;
|
const currentUserId = isSignedIn && user ? user.id : undefined;
|
||||||
@@ -115,7 +125,10 @@ export function usePartyKit() {
|
|||||||
sendClick,
|
sendClick,
|
||||||
purchaseUpgrade,
|
purchaseUpgrade,
|
||||||
sendMascotClickBonus,
|
sendMascotClickBonus,
|
||||||
|
sendMessage, // Expose sendMessage
|
||||||
|
lastMessage, // Expose lastMessage
|
||||||
userId: currentUserId,
|
userId: currentUserId,
|
||||||
userName: currentUserName
|
userName: currentUserName,
|
||||||
|
getToken // Expose getToken
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user