Add Daily Rewards & Time Trials features
NEW FEATURES:
- Daily login rewards with Gold and Cash
- Daily time trial racing events
- FREE gold purchase system
- Streak tracking for consecutive days
- Web panel for managing rewards and events
ENDPOINTS ADDED:
- GET/POST /synergy/rewards/daily/{id} - Daily rewards
- POST /synergy/rewards/gold/purchase - Buy gold (FREE)
- GET /synergy/rewards/timetrials - Active events
- POST /synergy/rewards/timetrials/{id}/submit - Submit times
DATABASE:
- DailyReward table - tracks claims and streaks
- TimeTrial table - racing events with rewards
- TimeTrialResult table - player submissions
- User.Gold and User.Cash - currency tracking
WEB PANEL:
- /admin/rewards - Manage all reward features
- Create/edit/activate time trial events
- View reward statistics and history
- Gold packages in catalog (100, 500, 1000, 5000)
FOCUS:
- Single-player progression features
- NO race teams or multiplayer
- Perfect for offline play
- All purchases are FREE in community server
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -100,6 +100,9 @@
|
||||
<a href="/admin/catalog" class="btn btn-info">
|
||||
<i class="bi bi-shop"></i> Manage Catalog
|
||||
</a>
|
||||
<a href="/admin/rewards" class="btn btn-warning">
|
||||
<i class="bi bi-gift"></i> Manage Rewards
|
||||
</a>
|
||||
<a href="/admin/sessions" class="btn btn-success">
|
||||
<i class="bi bi-clock-history"></i> View Sessions
|
||||
</a>
|
||||
|
||||
255
RR3CommunityServer/Pages/Rewards.cshtml
Normal file
255
RR3CommunityServer/Pages/Rewards.cshtml
Normal file
@@ -0,0 +1,255 @@
|
||||
@page
|
||||
@model RR3CommunityServer.Pages.RewardsModel
|
||||
@{
|
||||
ViewData["Title"] = "Daily Rewards & Time Trials";
|
||||
}
|
||||
|
||||
<div class="container-fluid mt-4">
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h1>🎁 Daily Rewards & Time Trials</h1>
|
||||
<p class="text-muted">Manage player rewards and racing events</p>
|
||||
</div>
|
||||
<a href="/admin" class="btn btn-outline-secondary">← Back to Dashboard</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Statistics -->
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-3">
|
||||
<div class="card border-success">
|
||||
<div class="card-body">
|
||||
<h6 class="text-muted">Today's Claims</h6>
|
||||
<h2 class="text-success">@Model.TodaysClaims</h2>
|
||||
<small>Daily rewards claimed</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card border-warning">
|
||||
<div class="card-body">
|
||||
<h6 class="text-muted">Active Time Trials</h6>
|
||||
<h2 class="text-warning">@Model.ActiveTimeTrials</h2>
|
||||
<small>Racing events live</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card border-info">
|
||||
<div class="card-body">
|
||||
<h6 class="text-muted">Gold Distributed</h6>
|
||||
<h2 class="text-info">@Model.TotalGoldDistributed.ToString("N0")</h2>
|
||||
<small>All time</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card border-primary">
|
||||
<div class="card-body">
|
||||
<h6 class="text-muted">Trial Completions</h6>
|
||||
<h2 class="text-primary">@Model.TrialCompletions</h2>
|
||||
<small>Total attempts</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Time Trials Management -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header bg-warning text-dark">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">⏱️ Time Trial Events</h5>
|
||||
<button class="btn btn-dark" data-bs-toggle="modal" data-bs-target="#addTrialModal">
|
||||
<i class="bi bi-plus-circle"></i> Add New Event
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@if (Model.TimeTrials.Any())
|
||||
{
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Event Name</th>
|
||||
<th>Track</th>
|
||||
<th>Target Time</th>
|
||||
<th>Rewards</th>
|
||||
<th>Period</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var trial in Model.TimeTrials)
|
||||
{
|
||||
<tr>
|
||||
<td><strong>@trial.Name</strong></td>
|
||||
<td>@trial.TrackName</td>
|
||||
<td><code>@trial.TargetTime.ToString("F2")s</code></td>
|
||||
<td>
|
||||
<span class="badge bg-warning">@trial.GoldReward G</span>
|
||||
<span class="badge bg-success">$@trial.CashReward</span>
|
||||
</td>
|
||||
<td>
|
||||
<small>@trial.StartDate.ToString("M/d") - @trial.EndDate.ToString("M/d")</small>
|
||||
</td>
|
||||
<td>
|
||||
@if (trial.Active)
|
||||
{
|
||||
<span class="badge bg-success">Active</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-secondary">Inactive</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
<form method="post" asp-page-handler="ToggleTrial" class="d-inline">
|
||||
<input type="hidden" name="trialId" value="@trial.Id" />
|
||||
<button type="submit" class="btn btn-sm btn-@(trial.Active ? "warning" : "success")">
|
||||
@(trial.Active ? "Deactivate" : "Activate")
|
||||
</button>
|
||||
</form>
|
||||
<form method="post" asp-page-handler="DeleteTrial" class="d-inline">
|
||||
<input type="hidden" name="trialId" value="@trial.Id" />
|
||||
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Delete this event?')">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="alert alert-info">
|
||||
No time trial events created yet. Add one to get started!
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Daily Rewards History -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header bg-success text-white">
|
||||
<h5 class="mb-0">📅 Recent Daily Reward Claims</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@if (Model.RecentRewards.Any())
|
||||
{
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User</th>
|
||||
<th>Gold</th>
|
||||
<th>Cash</th>
|
||||
<th>Streak</th>
|
||||
<th>Claimed</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var reward in Model.RecentRewards.Take(20))
|
||||
{
|
||||
<tr>
|
||||
<td><code>User @reward.UserId</code></td>
|
||||
<td><span class="badge bg-warning">@reward.GoldAmount</span></td>
|
||||
<td><span class="badge bg-success">@reward.CashAmount</span></td>
|
||||
<td>
|
||||
@if (reward.Streak > 0)
|
||||
{
|
||||
<span class="badge bg-info">🔥 @reward.Streak day(s)</span>
|
||||
}
|
||||
</td>
|
||||
<td><small>@reward.ClaimedAt?.ToString("g")</small></td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="alert alert-info">
|
||||
No daily rewards claimed yet.
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Time Trial Modal -->
|
||||
<div class="modal fade" id="addTrialModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form method="post" asp-page-handler="AddTrial">
|
||||
<div class="modal-header bg-warning">
|
||||
<h5 class="modal-title">Add Time Trial Event</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Event Name</label>
|
||||
<input type="text" name="name" class="form-control" placeholder="Daily Sprint Challenge" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Track Name</label>
|
||||
<input type="text" name="trackName" class="form-control" placeholder="Silverstone National" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Car Requirement</label>
|
||||
<input type="text" name="carName" class="form-control" value="Any Car" required>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Start Date</label>
|
||||
<input type="date" name="startDate" class="form-control" value="@DateTime.UtcNow.ToString("yyyy-MM-dd")" required>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">End Date</label>
|
||||
<input type="date" name="endDate" class="form-control" value="@DateTime.UtcNow.AddDays(7).ToString("yyyy-MM-dd")" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Target Time (seconds)</label>
|
||||
<input type="number" name="targetTime" step="0.1" class="form-control" placeholder="90.5" required>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Gold Reward</label>
|
||||
<input type="number" name="goldReward" class="form-control" value="50" required>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Cash Reward</label>
|
||||
<input type="number" name="cashReward" class="form-control" value="10000" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-warning">Create Event</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
102
RR3CommunityServer/Pages/Rewards.cshtml.cs
Normal file
102
RR3CommunityServer/Pages/Rewards.cshtml.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RR3CommunityServer.Data;
|
||||
using static RR3CommunityServer.Data.RR3DbContext;
|
||||
|
||||
namespace RR3CommunityServer.Pages;
|
||||
|
||||
public class RewardsModel : PageModel
|
||||
{
|
||||
private readonly RR3DbContext _context;
|
||||
|
||||
public RewardsModel(RR3DbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public int TodaysClaims { get; set; }
|
||||
public int ActiveTimeTrials { get; set; }
|
||||
public int TotalGoldDistributed { get; set; }
|
||||
public int TrialCompletions { get; set; }
|
||||
public List<TimeTrial> TimeTrials { get; set; } = new();
|
||||
public List<DailyReward> RecentRewards { get; set; } = new();
|
||||
|
||||
public async Task OnGetAsync()
|
||||
{
|
||||
var today = DateTime.UtcNow.Date;
|
||||
|
||||
TodaysClaims = await _context.DailyRewards
|
||||
.Where(r => r.RewardDate.Date == today && r.Claimed)
|
||||
.CountAsync();
|
||||
|
||||
ActiveTimeTrials = await _context.TimeTrials
|
||||
.Where(t => t.Active)
|
||||
.CountAsync();
|
||||
|
||||
TotalGoldDistributed = await _context.DailyRewards
|
||||
.Where(r => r.Claimed)
|
||||
.SumAsync(r => r.GoldAmount);
|
||||
|
||||
TrialCompletions = await _context.TimeTrialResults.CountAsync();
|
||||
|
||||
TimeTrials = await _context.TimeTrials
|
||||
.OrderByDescending(t => t.Active)
|
||||
.ThenByDescending(t => t.StartDate)
|
||||
.ToListAsync();
|
||||
|
||||
RecentRewards = await _context.DailyRewards
|
||||
.Where(r => r.Claimed)
|
||||
.OrderByDescending(r => r.ClaimedAt)
|
||||
.Take(20)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostAddTrialAsync(
|
||||
string name, string trackName, string carName,
|
||||
DateTime startDate, DateTime endDate,
|
||||
double targetTime, int goldReward, int cashReward)
|
||||
{
|
||||
var trial = new TimeTrial
|
||||
{
|
||||
Name = name,
|
||||
TrackName = trackName,
|
||||
CarName = carName,
|
||||
StartDate = startDate,
|
||||
EndDate = endDate,
|
||||
TargetTime = targetTime,
|
||||
GoldReward = goldReward,
|
||||
CashReward = cashReward,
|
||||
Active = true
|
||||
};
|
||||
|
||||
_context.TimeTrials.Add(trial);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return RedirectToPage();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostToggleTrialAsync(int trialId)
|
||||
{
|
||||
var trial = await _context.TimeTrials.FindAsync(trialId);
|
||||
if (trial != null)
|
||||
{
|
||||
trial.Active = !trial.Active;
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return RedirectToPage();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostDeleteTrialAsync(int trialId)
|
||||
{
|
||||
var trial = await _context.TimeTrials.FindAsync(trialId);
|
||||
if (trial != null)
|
||||
{
|
||||
_context.TimeTrials.Remove(trial);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return RedirectToPage();
|
||||
}
|
||||
}
|
||||
@@ -93,6 +93,11 @@
|
||||
<i class="bi bi-clock-history"></i> Sessions
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/admin/rewards">
|
||||
<i class="bi bi-gift"></i> Rewards
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/admin/purchases">
|
||||
<i class="bi bi-cart"></i> Purchases
|
||||
|
||||
Reference in New Issue
Block a user