added ban redirect correctly
All checks were successful
Build Frontend / Build Frontend (push) Successful in 25s
All checks were successful
Build Frontend / Build Frontend (push) Successful in 25s
This commit is contained in:
277
BAN_NOTIFICATION_FIX.md
Normal file
277
BAN_NOTIFICATION_FIX.md
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
# Ban Notification Feature - Deployment Guide
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Fixed the issue where banned users only saw a toast notification instead of being redirected to the banned page. Now when a user is banned from the admin panel, they are **immediately redirected** via WebSocket notification.
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
When an admin banned a user:
|
||||||
|
1. User's account was updated in database ✅
|
||||||
|
2. User saw a toast notification ❌ (not helpful)
|
||||||
|
3. User could continue using the site until page refresh ❌
|
||||||
|
4. `/api/auth/me` returned 403, preventing frontend from getting ban info ❌
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
### 1. Real-time WebSocket Notifications
|
||||||
|
- Backend sends `account_banned` event to user's active WebSocket connection
|
||||||
|
- Frontend receives event and refreshes auth state
|
||||||
|
- User is immediately redirected to `/banned` page
|
||||||
|
|
||||||
|
### 2. Allow Banned Users to Access `/api/auth/me`
|
||||||
|
- Modified `middleware/auth.js` to allow banned users to access `/api/auth/me`
|
||||||
|
- This lets the frontend fetch ban details and redirect properly
|
||||||
|
- All other endpoints remain blocked for banned users
|
||||||
|
|
||||||
|
## Changes Made
|
||||||
|
|
||||||
|
### Backend (`routes/admin-management.js`)
|
||||||
|
```javascript
|
||||||
|
// Added WebSocket import
|
||||||
|
import wsManager from "../utils/websocket.js";
|
||||||
|
|
||||||
|
// In ban handler, after saving user:
|
||||||
|
if (wsManager.isUserConnected(user.steamId)) {
|
||||||
|
if (banned) {
|
||||||
|
wsManager.sendToUser(user.steamId, {
|
||||||
|
type: "account_banned",
|
||||||
|
data: {
|
||||||
|
banned: true,
|
||||||
|
reason: user.ban.reason,
|
||||||
|
bannedAt: new Date(),
|
||||||
|
bannedUntil: user.ban.expires,
|
||||||
|
},
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
wsManager.sendToUser(user.steamId, {
|
||||||
|
type: "account_unbanned",
|
||||||
|
data: { banned: false },
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backend (`middleware/auth.js`)
|
||||||
|
```javascript
|
||||||
|
// Allow banned users to access /api/auth/me
|
||||||
|
const url = request.url || "";
|
||||||
|
const routeUrl = request.routeOptions?.url || "";
|
||||||
|
const isAuthMeEndpoint =
|
||||||
|
url.includes("/auth/me") ||
|
||||||
|
routeUrl === "/me" ||
|
||||||
|
routeUrl.endsWith("/me");
|
||||||
|
|
||||||
|
if (!isAuthMeEndpoint) {
|
||||||
|
// Block access to all other endpoints
|
||||||
|
return reply.status(403).send({ /* ban error */ });
|
||||||
|
}
|
||||||
|
// If it's /api/auth/me, continue and attach user with ban info
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontend (`frontend/src/stores/websocket.js`)
|
||||||
|
```javascript
|
||||||
|
case "account_banned":
|
||||||
|
console.log("🔨 User account has been banned:", payload);
|
||||||
|
// Update the auth store - router guard will handle redirect
|
||||||
|
authStore.fetchUser().then(() => {
|
||||||
|
// Disconnect WebSocket since user is banned
|
||||||
|
disconnect();
|
||||||
|
// The router guard will automatically redirect to /banned page
|
||||||
|
window.location.href = "/banned";
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "account_unbanned":
|
||||||
|
console.log("✅ User account has been unbanned:", payload);
|
||||||
|
// Update the auth store to reflect unbanned status
|
||||||
|
authStore.fetchUser().then(() => {
|
||||||
|
window.location.href = "/";
|
||||||
|
toast.success("Your account has been unbanned. Welcome back!");
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
```
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
### Ban Flow
|
||||||
|
1. **Admin bans user** → Clicks "Ban" button in admin panel
|
||||||
|
2. **Backend saves ban** → Updates User record in MongoDB
|
||||||
|
3. **WebSocket notification** → Server sends `account_banned` event
|
||||||
|
4. **Frontend receives** → WebSocket store handles the event
|
||||||
|
5. **Auth refresh** → Calls `/api/auth/me` (now allowed for banned users)
|
||||||
|
6. **Router guard** → Sees `authStore.isBanned = true`
|
||||||
|
7. **Redirect** → User sent to `/banned` page immediately
|
||||||
|
|
||||||
|
### Unban Flow
|
||||||
|
1. **Admin unbans user** → Clicks "Unban" button
|
||||||
|
2. **Backend clears ban** → Removes ban from User record
|
||||||
|
3. **WebSocket notification** → Server sends `account_unbanned` event
|
||||||
|
4. **Frontend receives** → Refreshes auth and redirects to home
|
||||||
|
5. **Success toast** → "Your account has been unbanned. Welcome back!"
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Test Ban Notification
|
||||||
|
1. Open browser window as User A (logged in)
|
||||||
|
2. Open another window as Admin
|
||||||
|
3. Admin bans User A from admin panel
|
||||||
|
4. User A should **immediately** be redirected to `/banned` page
|
||||||
|
5. `/banned` page shows:
|
||||||
|
- Account suspended message
|
||||||
|
- Ban reason
|
||||||
|
- Ban duration (or "permanent")
|
||||||
|
- Contact support button
|
||||||
|
- Logout button
|
||||||
|
|
||||||
|
### Test Unban Notification
|
||||||
|
1. User A is on `/banned` page
|
||||||
|
2. Admin unbans User A
|
||||||
|
3. User A should **immediately** be redirected to home page
|
||||||
|
4. Success toast appears
|
||||||
|
|
||||||
|
### Test Without WebSocket
|
||||||
|
1. User A is logged in but WebSocket disconnected
|
||||||
|
2. Admin bans User A
|
||||||
|
3. User A continues using site temporarily
|
||||||
|
4. On next page navigation, router guard catches ban
|
||||||
|
5. User A redirected to `/banned` page
|
||||||
|
|
||||||
|
## Deployment Steps
|
||||||
|
|
||||||
|
### 1. Deploy Backend
|
||||||
|
```bash
|
||||||
|
# SSH to production server
|
||||||
|
ssh user@api.turbotrades.dev
|
||||||
|
|
||||||
|
# Navigate to project
|
||||||
|
cd /path/to/TurboTrades
|
||||||
|
|
||||||
|
# Pull latest changes (if using git)
|
||||||
|
git pull
|
||||||
|
|
||||||
|
# Or manually upload files:
|
||||||
|
# - routes/admin-management.js
|
||||||
|
# - middleware/auth.js
|
||||||
|
|
||||||
|
# Restart backend
|
||||||
|
pm2 restart turbotrades-backend
|
||||||
|
|
||||||
|
# Verify backend is running
|
||||||
|
pm2 logs turbotrades-backend --lines 50
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Deploy Frontend
|
||||||
|
```bash
|
||||||
|
# On your local machine (Windows)
|
||||||
|
cd C:\Users\dg-ho\Documents\projects\TurboTrades\frontend
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# On production server
|
||||||
|
cd /path/to/TurboTrades/frontend
|
||||||
|
npm run build
|
||||||
|
sudo cp -r dist/* /var/www/html/turbotrades/
|
||||||
|
|
||||||
|
# Verify files copied
|
||||||
|
ls -la /var/www/html/turbotrades/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Test in Production
|
||||||
|
```bash
|
||||||
|
# Check backend logs for WebSocket connections
|
||||||
|
pm2 logs turbotrades-backend --lines 100 | grep WebSocket
|
||||||
|
|
||||||
|
# Test ban flow
|
||||||
|
curl -X POST https://api.turbotrades.dev/api/admin/users/{userId}/ban \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Cookie: accessToken=YOUR_ADMIN_TOKEN" \
|
||||||
|
-d '{"banned": true, "reason": "Test ban", "duration": 1}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Files Changed
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
- ✅ `routes/admin-management.js` - Added WebSocket ban notifications
|
||||||
|
- ✅ `middleware/auth.js` - Allow banned users to access `/api/auth/me`
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
- ✅ `frontend/src/stores/websocket.js` - Handle ban/unban events
|
||||||
|
- ✅ `frontend/src/views/BannedPage.vue` - Already exists (no changes needed)
|
||||||
|
- ✅ `frontend/src/router/index.js` - Router guard already exists (no changes needed)
|
||||||
|
|
||||||
|
## Logs to Watch
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
```
|
||||||
|
📡 Sent ban notification to user {username}
|
||||||
|
📡 Sent unban notification to user {username}
|
||||||
|
🔨 Admin {admin} banned user {user} (Reason: {reason})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontend Console
|
||||||
|
```
|
||||||
|
🔨 User account has been banned: {banInfo}
|
||||||
|
✅ User account has been unbanned: {unbanInfo}
|
||||||
|
🔵 Calling authStore.fetchUser() from WebSocket connected handler
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### User not redirected after ban
|
||||||
|
1. Check if WebSocket is connected: `wsManager.isUserConnected(steamId)`
|
||||||
|
2. Check browser console for WebSocket messages
|
||||||
|
3. Verify `/api/auth/me` returns 200 with ban info (not 403)
|
||||||
|
|
||||||
|
### 403 Error on `/api/auth/me`
|
||||||
|
1. Check `middleware/auth.js` deployed correctly
|
||||||
|
2. Verify URL matching logic: `url.includes("/auth/me")`
|
||||||
|
3. Backend logs should show: `✓ User authenticated: {username}`
|
||||||
|
|
||||||
|
### Ban page not showing
|
||||||
|
1. Verify `authStore.isBanned` is true: Check console
|
||||||
|
2. Check router guard in `frontend/src/router/index.js`
|
||||||
|
3. Clear browser cache / hard refresh (Ctrl+Shift+R)
|
||||||
|
|
||||||
|
### WebSocket not connected
|
||||||
|
1. Check WebSocket URL: `wss://api.turbotrades.dev/ws`
|
||||||
|
2. Verify Nginx WebSocket proxy configuration
|
||||||
|
3. Check backend WebSocket server is running
|
||||||
|
|
||||||
|
## Security Notes
|
||||||
|
|
||||||
|
✅ **Banned users can only access `/api/auth/me`**
|
||||||
|
- All other endpoints return 403
|
||||||
|
- Prevents banned users from trading, depositing, etc.
|
||||||
|
|
||||||
|
✅ **Admins cannot ban other admins**
|
||||||
|
- Check in ban handler: `user.staffLevel >= 3`
|
||||||
|
|
||||||
|
✅ **WebSocket notifications only sent to connected users**
|
||||||
|
- Offline users will see ban on next page load via router guard
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- [ ] Add ban notification to email (optional)
|
||||||
|
- [ ] Log ban actions for audit trail (already logged to console)
|
||||||
|
- [ ] Add ban appeal form on `/banned` page (optional)
|
||||||
|
- [ ] Add bulk ban WebSocket notifications (if implementing bulk ban)
|
||||||
|
|
||||||
|
## Related Files
|
||||||
|
- `frontend/src/views/BannedPage.vue` - The banned page UI
|
||||||
|
- `frontend/src/router/index.js` - Router guard for ban redirect
|
||||||
|
- `frontend/src/stores/auth.js` - Auth store with `isBanned` computed
|
||||||
|
- `routes/admin-management.js` - Admin ban/unban endpoints
|
||||||
|
- `middleware/auth.js` - Authentication middleware
|
||||||
|
- `utils/websocket.js` - WebSocket manager
|
||||||
|
|
||||||
|
## Deployment Checklist
|
||||||
|
|
||||||
|
- [ ] Backend code updated
|
||||||
|
- [ ] Frontend code updated
|
||||||
|
- [ ] Frontend built (`npm run build`)
|
||||||
|
- [ ] Backend restarted (`pm2 restart`)
|
||||||
|
- [ ] Frontend deployed to web root
|
||||||
|
- [ ] Tested ban flow (admin bans user → user redirected)
|
||||||
|
- [ ] Tested unban flow (admin unbans → user redirected)
|
||||||
|
- [ ] Verified `/api/auth/me` works for banned users
|
||||||
|
- [ ] Checked browser console for errors
|
||||||
|
- [ ] Checked backend logs for WebSocket messages
|
||||||
176
deploy-ban-fix.sh
Normal file
176
deploy-ban-fix.sh
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Deploy Ban Notification Fix
|
||||||
|
# This script deploys the ban notification feature to production
|
||||||
|
|
||||||
|
set -e # Exit on error
|
||||||
|
|
||||||
|
echo "🚀 Deploying Ban Notification Fix..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
PROJECT_DIR="/path/to/TurboTrades"
|
||||||
|
FRONTEND_DIR="$PROJECT_DIR/frontend"
|
||||||
|
WEB_ROOT="/var/www/html/turbotrades"
|
||||||
|
PM2_APP_NAME="turbotrades-backend"
|
||||||
|
|
||||||
|
# Check if running on production server
|
||||||
|
if [[ ! -d "$PROJECT_DIR" ]]; then
|
||||||
|
echo -e "${RED}❌ Error: Project directory not found at $PROJECT_DIR${NC}"
|
||||||
|
echo "Please update PROJECT_DIR in this script to match your server setup"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 1: Backup current deployment
|
||||||
|
echo -e "${YELLOW}📦 Creating backup...${NC}"
|
||||||
|
BACKUP_DIR="$HOME/turbotrades-backup-$(date +%Y%m%d-%H%M%S)"
|
||||||
|
mkdir -p "$BACKUP_DIR"
|
||||||
|
|
||||||
|
# Backup backend files
|
||||||
|
cp "$PROJECT_DIR/routes/admin-management.js" "$BACKUP_DIR/admin-management.js.bak" 2>/dev/null || true
|
||||||
|
cp "$PROJECT_DIR/middleware/auth.js" "$BACKUP_DIR/auth.js.bak" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Backup frontend
|
||||||
|
if [[ -d "$WEB_ROOT" ]]; then
|
||||||
|
cp -r "$WEB_ROOT" "$BACKUP_DIR/frontend" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}✓ Backup created at $BACKUP_DIR${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 2: Pull latest changes (if using git)
|
||||||
|
cd "$PROJECT_DIR"
|
||||||
|
if [[ -d ".git" ]]; then
|
||||||
|
echo -e "${YELLOW}📥 Pulling latest changes from git...${NC}"
|
||||||
|
git pull
|
||||||
|
echo -e "${GREEN}✓ Git pull complete${NC}"
|
||||||
|
echo ""
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}⚠️ Not a git repository, skipping git pull${NC}"
|
||||||
|
echo -e "${YELLOW} Make sure you've manually uploaded the updated files:${NC}"
|
||||||
|
echo " - routes/admin-management.js"
|
||||||
|
echo " - middleware/auth.js"
|
||||||
|
echo " - frontend/src/stores/websocket.js"
|
||||||
|
echo ""
|
||||||
|
read -p "Press Enter to continue after uploading files..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 3: Install/update dependencies (if needed)
|
||||||
|
echo -e "${YELLOW}📦 Checking dependencies...${NC}"
|
||||||
|
cd "$PROJECT_DIR"
|
||||||
|
if [[ -f "package.json" ]]; then
|
||||||
|
npm install --production 2>/dev/null || echo "Dependencies already up to date"
|
||||||
|
fi
|
||||||
|
echo -e "${GREEN}✓ Dependencies checked${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 4: Build frontend
|
||||||
|
echo -e "${YELLOW}🔨 Building frontend...${NC}"
|
||||||
|
cd "$FRONTEND_DIR"
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
if [[ ! -d "dist" ]]; then
|
||||||
|
echo -e "${RED}❌ Error: Frontend build failed (dist directory not found)${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}✓ Frontend built successfully${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 5: Deploy frontend
|
||||||
|
echo -e "${YELLOW}🚀 Deploying frontend...${NC}"
|
||||||
|
if [[ ! -d "$WEB_ROOT" ]]; then
|
||||||
|
echo "Creating web root directory..."
|
||||||
|
sudo mkdir -p "$WEB_ROOT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo cp -r dist/* "$WEB_ROOT/"
|
||||||
|
echo -e "${GREEN}✓ Frontend deployed to $WEB_ROOT${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 6: Restart backend
|
||||||
|
echo -e "${YELLOW}🔄 Restarting backend...${NC}"
|
||||||
|
cd "$PROJECT_DIR"
|
||||||
|
|
||||||
|
# Check if PM2 is available
|
||||||
|
if command -v pm2 &> /dev/null; then
|
||||||
|
pm2 restart "$PM2_APP_NAME"
|
||||||
|
|
||||||
|
# Wait a moment for restart
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# Check if process is running
|
||||||
|
if pm2 list | grep -q "$PM2_APP_NAME"; then
|
||||||
|
echo -e "${GREEN}✓ Backend restarted successfully${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${RED}❌ Warning: Backend may not be running${NC}"
|
||||||
|
echo "Check logs with: pm2 logs $PM2_APP_NAME"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}⚠️ PM2 not found, please restart backend manually${NC}"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 7: Verify deployment
|
||||||
|
echo -e "${YELLOW}🔍 Verifying deployment...${NC}"
|
||||||
|
|
||||||
|
# Check if backend is responding
|
||||||
|
BACKEND_URL="http://localhost:3000/api/health"
|
||||||
|
if curl -s "$BACKEND_URL" > /dev/null 2>&1; then
|
||||||
|
echo -e "${GREEN}✓ Backend is responding${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${RED}❌ Warning: Backend health check failed${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if frontend files exist
|
||||||
|
if [[ -f "$WEB_ROOT/index.html" ]]; then
|
||||||
|
echo -e "${GREEN}✓ Frontend files deployed${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${RED}❌ Warning: Frontend index.html not found${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 8: Display logs
|
||||||
|
echo -e "${YELLOW}📋 Recent backend logs:${NC}"
|
||||||
|
if command -v pm2 &> /dev/null; then
|
||||||
|
pm2 logs "$PM2_APP_NAME" --lines 20 --nostream
|
||||||
|
else
|
||||||
|
echo "PM2 not available, skipping logs"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Success message
|
||||||
|
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||||
|
echo -e "${GREEN}✅ Ban Notification Fix Deployed Successfully!${NC}"
|
||||||
|
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}Next Steps:${NC}"
|
||||||
|
echo "1. Test the ban flow:"
|
||||||
|
echo " - Login as a user in one browser"
|
||||||
|
echo " - Login as admin in another browser"
|
||||||
|
echo " - Ban the user from admin panel"
|
||||||
|
echo " - User should be immediately redirected to /banned page"
|
||||||
|
echo ""
|
||||||
|
echo "2. Check the logs:"
|
||||||
|
echo " pm2 logs $PM2_APP_NAME --lines 50"
|
||||||
|
echo ""
|
||||||
|
echo "3. Monitor WebSocket connections:"
|
||||||
|
echo " pm2 logs $PM2_APP_NAME | grep WebSocket"
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}Backup Location:${NC} $BACKUP_DIR"
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}Rollback Instructions:${NC}"
|
||||||
|
echo "If something goes wrong, restore from backup:"
|
||||||
|
echo " cp $BACKUP_DIR/admin-management.js.bak $PROJECT_DIR/routes/admin-management.js"
|
||||||
|
echo " cp $BACKUP_DIR/auth.js.bak $PROJECT_DIR/middleware/auth.js"
|
||||||
|
echo " sudo cp -r $BACKUP_DIR/frontend/* $WEB_ROOT/"
|
||||||
|
echo " pm2 restart $PM2_APP_NAME"
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}Happy banning! 🔨${NC}"
|
||||||
@@ -496,6 +496,80 @@
|
|||||||
<p class="form-help">How often to auto-update market prices</p>
|
<p class="form-help">How often to auto-update market prices</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-section">
|
||||||
|
<h3>💵 Instant Sell Payout</h3>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Global Payout Rate (%)</label>
|
||||||
|
<input
|
||||||
|
v-model.number="instantSellForm.payoutRate"
|
||||||
|
type="number"
|
||||||
|
step="0.1"
|
||||||
|
min="0"
|
||||||
|
max="100"
|
||||||
|
class="form-input"
|
||||||
|
placeholder="70.0"
|
||||||
|
/>
|
||||||
|
<p class="form-help">
|
||||||
|
Default percentage of market price paid to users (e.g., 70 = pay
|
||||||
|
70% of item value)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>CS2 Payout Rate (%)</label>
|
||||||
|
<input
|
||||||
|
v-model.number="instantSellForm.cs2.payoutRate"
|
||||||
|
type="number"
|
||||||
|
step="0.1"
|
||||||
|
min="0"
|
||||||
|
max="100"
|
||||||
|
class="form-input"
|
||||||
|
placeholder="70.0"
|
||||||
|
/>
|
||||||
|
<p class="form-help">
|
||||||
|
Payout rate specifically for CS2 items (overrides global)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Rust Payout Rate (%)</label>
|
||||||
|
<input
|
||||||
|
v-model.number="instantSellForm.rust.payoutRate"
|
||||||
|
type="number"
|
||||||
|
step="0.1"
|
||||||
|
min="0"
|
||||||
|
max="100"
|
||||||
|
class="form-input"
|
||||||
|
placeholder="70.0"
|
||||||
|
/>
|
||||||
|
<p class="form-help">
|
||||||
|
Payout rate specifically for Rust items (overrides global)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Min Item Value ($)</label>
|
||||||
|
<input
|
||||||
|
v-model.number="instantSellForm.minItemValue"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
class="form-input"
|
||||||
|
placeholder="0.01"
|
||||||
|
/>
|
||||||
|
<p class="form-help">Minimum item value for instant sell</p>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Max Item Value ($)</label>
|
||||||
|
<input
|
||||||
|
v-model.number="instantSellForm.maxItemValue"
|
||||||
|
type="number"
|
||||||
|
step="1"
|
||||||
|
min="0"
|
||||||
|
class="form-input"
|
||||||
|
placeholder="10000"
|
||||||
|
/>
|
||||||
|
<p class="form-help">Maximum item value for instant sell</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-actions-centered">
|
<div class="form-actions-centered">
|
||||||
@@ -970,6 +1044,22 @@ const promotionForm = ref({
|
|||||||
code: "",
|
code: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Instant Sell form
|
||||||
|
const instantSellForm = ref({
|
||||||
|
enabled: true,
|
||||||
|
payoutRate: 70.0, // Percentage (70 = 70%)
|
||||||
|
minItemValue: 0.01,
|
||||||
|
maxItemValue: 10000,
|
||||||
|
cs2: {
|
||||||
|
enabled: true,
|
||||||
|
payoutRate: 70.0,
|
||||||
|
},
|
||||||
|
rust: {
|
||||||
|
enabled: true,
|
||||||
|
payoutRate: 70.0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
const loadConfig = async () => {
|
const loadConfig = async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
@@ -1016,6 +1106,25 @@ const loadConfig = async () => {
|
|||||||
commission: config.value.market.commission * 100,
|
commission: config.value.market.commission * 100,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.value.instantSell) {
|
||||||
|
instantSellForm.value = {
|
||||||
|
enabled: config.value.instantSell.enabled ?? true,
|
||||||
|
// Convert decimal to percentage for display (0.7 -> 70)
|
||||||
|
payoutRate: (config.value.instantSell.payoutRate ?? 0.7) * 100,
|
||||||
|
minItemValue: config.value.instantSell.minItemValue ?? 0.01,
|
||||||
|
maxItemValue: config.value.instantSell.maxItemValue ?? 10000,
|
||||||
|
cs2: {
|
||||||
|
enabled: config.value.instantSell.cs2?.enabled ?? true,
|
||||||
|
payoutRate: (config.value.instantSell.cs2?.payoutRate ?? 0.7) * 100,
|
||||||
|
},
|
||||||
|
rust: {
|
||||||
|
enabled: config.value.instantSell.rust?.enabled ?? true,
|
||||||
|
payoutRate:
|
||||||
|
(config.value.instantSell.rust?.payoutRate ?? 0.7) * 100,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to load config:", error);
|
console.error("Failed to load config:", error);
|
||||||
@@ -1060,25 +1169,46 @@ const saveAllSettings = async () => {
|
|||||||
commission: marketForm.value.commission / 100, // Convert percentage to decimal
|
commission: marketForm.value.commission / 100, // Convert percentage to decimal
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const instantSellData = {
|
||||||
|
enabled: instantSellForm.value.enabled,
|
||||||
|
payoutRate: instantSellForm.value.payoutRate / 100, // Convert percentage to decimal
|
||||||
|
minItemValue: instantSellForm.value.minItemValue,
|
||||||
|
maxItemValue: instantSellForm.value.maxItemValue,
|
||||||
|
cs2: {
|
||||||
|
enabled: instantSellForm.value.cs2.enabled,
|
||||||
|
payoutRate: instantSellForm.value.cs2.payoutRate / 100,
|
||||||
|
},
|
||||||
|
rust: {
|
||||||
|
enabled: instantSellForm.value.rust.enabled,
|
||||||
|
payoutRate: instantSellForm.value.rust.payoutRate / 100,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
console.log("💾 Saving all settings...");
|
console.log("💾 Saving all settings...");
|
||||||
console.log("Trading data:", tradingData);
|
console.log("Trading data:", tradingData);
|
||||||
console.log("Market data:", marketData);
|
console.log("Market data:", marketData);
|
||||||
|
console.log("Instant Sell data:", instantSellData);
|
||||||
|
|
||||||
// Save both in parallel
|
// Save all in parallel
|
||||||
const [tradingResponse, marketResponse] = await Promise.all([
|
const [tradingResponse, marketResponse, instantSellResponse] =
|
||||||
axios.patch("/api/admin/config/trading", tradingData),
|
await Promise.all([
|
||||||
axios.patch("/api/admin/config/market", marketData),
|
axios.patch("/api/admin/config/trading", tradingData),
|
||||||
]);
|
axios.patch("/api/admin/config/market", marketData),
|
||||||
|
axios.patch("/api/admin/config/instantsell", instantSellData),
|
||||||
|
]);
|
||||||
|
|
||||||
if (tradingResponse.data.success && marketResponse.data.success) {
|
if (
|
||||||
toast.success("✅ All settings saved successfully!");
|
tradingResponse.data.success &&
|
||||||
await loadConfig();
|
marketResponse.data.success &&
|
||||||
|
instantSellResponse.data.success
|
||||||
|
) {
|
||||||
|
toast.success("Settings saved successfully");
|
||||||
|
await loadConfig(); // Reload to get updated values
|
||||||
} else {
|
} else {
|
||||||
throw new Error("One or more settings failed to save");
|
toast.error("Failed to save some settings");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("❌ Failed to save settings:", error);
|
console.error("Failed to save settings:", error);
|
||||||
console.error("Error response:", error.response?.data);
|
|
||||||
toast.error(error.response?.data?.message || "Failed to save settings");
|
toast.error(error.response?.data?.message || "Failed to save settings");
|
||||||
} finally {
|
} finally {
|
||||||
saving.value = false;
|
saving.value = false;
|
||||||
@@ -1885,7 +2015,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
.settings-grid {
|
.settings-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
gap: 2rem;
|
gap: 2rem;
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
}
|
}
|
||||||
@@ -1986,6 +2116,12 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1400px) {
|
||||||
|
.settings-grid {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 1024px) {
|
@media (max-width: 1024px) {
|
||||||
.settings-grid {
|
.settings-grid {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
|
|||||||
@@ -9,12 +9,6 @@
|
|||||||
<!-- Title -->
|
<!-- Title -->
|
||||||
<h1 class="banned-title">Account Suspended</h1>
|
<h1 class="banned-title">Account Suspended</h1>
|
||||||
|
|
||||||
<!-- Message -->
|
|
||||||
<p class="banned-message">
|
|
||||||
Your account has been suspended due to a violation of our Terms of
|
|
||||||
Service.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<!-- Ban Details -->
|
<!-- Ban Details -->
|
||||||
<div v-if="banInfo" class="ban-details">
|
<div v-if="banInfo" class="ban-details">
|
||||||
<div class="detail-item">
|
<div class="detail-item">
|
||||||
|
|||||||
Reference in New Issue
Block a user