mirror of
https://github.com/CannedNet/CannedNet.git
synced 2026-10-27 16:09:03 -07:00
basic admin panel stuff
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
@page "/rooms/create"
|
||||
@rendermode InteractiveServer
|
||||
|
||||
<PageTitle>Create Room</PageTitle>
|
||||
|
||||
<h3>Create Room</h3>
|
||||
<p class="text-muted">Create a new room and optionally a default subroom.</p>
|
||||
|
||||
<form>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">Room Details</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Room ID (leave 0 for auto)</label>
|
||||
<input type="number" class="form-control" @bind="roomId" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Name <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" @bind="name" required />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Description</label>
|
||||
<textarea class="form-control" rows="3" @bind="description"></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Creator Account ID</label>
|
||||
<input type="number" class="form-control" @bind="creatorAccountId" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Image Name</label>
|
||||
<input type="text" class="form-control" @bind="imageName" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Accessibility</label>
|
||||
<input type="number" class="form-control" @bind="accessibility" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">SubRoom</div>
|
||||
<div class="card-body">
|
||||
<div class="form-check mb-3">
|
||||
<input type="checkbox" class="form-check-input" @bind="createSubRoom" />
|
||||
<label class="form-check-label">Create a default subroom</label>
|
||||
</div>
|
||||
|
||||
@if (createSubRoom)
|
||||
{
|
||||
<div class="mb-3">
|
||||
<label class="form-label">SubRoom Name</label>
|
||||
<input type="text" class="form-control" @bind="subRoomName" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Max Players</label>
|
||||
<input type="number" class="form-control" @bind="maxPlayers" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">Platform Support</div>
|
||||
<div class="card-body">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" @bind="supportsVRLow" />
|
||||
<label class="form-check-label">VR Low</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" @bind="supportsQuest2" />
|
||||
<label class="form-check-label">Quest 2</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" @bind="supportsMobile" />
|
||||
<label class="form-check-label">Mobile</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" @bind="supportsScreens" />
|
||||
<label class="form-check-label">Screens</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" @bind="supportsWalkVR" />
|
||||
<label class="form-check-label">Walk VR</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" @bind="supportsTeleportVR" />
|
||||
<label class="form-check-label">Teleport VR</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" @bind="supportsJuniors" />
|
||||
<label class="form-check-label">Supports Juniors</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary" @onclick="CreateRoom" disabled="@creating">
|
||||
@(creating ? "Creating..." : "Create Room")
|
||||
</button>
|
||||
<a href="rooms/search" class="btn btn-outline-secondary ms-2">Cancel</a>
|
||||
|
||||
@if (!string.IsNullOrEmpty(message))
|
||||
{
|
||||
<div class="alert mt-3 @(message.Contains("Error") ? "alert-danger" : "alert-success")">@message</div>
|
||||
}
|
||||
|
||||
@if (createdRoom != null)
|
||||
{
|
||||
<div class="alert alert-success mt-2">
|
||||
Room created! <a href="rooms/edit/@createdRoom.RoomId">Edit Room #@createdRoom.RoomId</a>
|
||||
</div>
|
||||
}
|
||||
</form>
|
||||
|
||||
@code {
|
||||
[Inject] private HttpClient Http { get; set; } = default!;
|
||||
|
||||
private bool creating;
|
||||
private string? message;
|
||||
private Room? createdRoom;
|
||||
|
||||
private int roomId;
|
||||
private string name = "";
|
||||
private string description = "";
|
||||
private int creatorAccountId;
|
||||
private string imageName = "";
|
||||
private int accessibility;
|
||||
private bool createSubRoom = true;
|
||||
private string subRoomName = "";
|
||||
private int maxPlayers = 16;
|
||||
private bool supportsVRLow = true;
|
||||
private bool supportsQuest2 = true;
|
||||
private bool supportsMobile = true;
|
||||
private bool supportsScreens = true;
|
||||
private bool supportsWalkVR = true;
|
||||
private bool supportsTeleportVR = true;
|
||||
private bool supportsJuniors = true;
|
||||
|
||||
private async Task CreateRoom()
|
||||
{
|
||||
creating = true;
|
||||
message = null;
|
||||
try
|
||||
{
|
||||
var payload = new
|
||||
{
|
||||
roomId = roomId > 0 ? roomId : 0,
|
||||
name,
|
||||
description,
|
||||
creatorAccountId,
|
||||
imageName,
|
||||
accessibility,
|
||||
createSubRoom,
|
||||
subRoomName = createSubRoom ? (string.IsNullOrWhiteSpace(subRoomName) ? name : subRoomName) : "",
|
||||
maxPlayers
|
||||
};
|
||||
var response = await Http.PostAsJsonAsync("/admin/api/rooms", payload);
|
||||
response.EnsureSuccessStatusCode();
|
||||
createdRoom = await response.Content.ReadFromJsonAsync<Room>();
|
||||
message = $"Room '{createdRoom?.Name}' created successfully!";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
message = $"Error: {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
creating = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
@page "/rooms/edit/{RoomId:int}"
|
||||
@rendermode InteractiveServer
|
||||
@inject ImageUrlService Img
|
||||
|
||||
<PageTitle>Edit Room @RoomId</PageTitle>
|
||||
|
||||
<h3>Edit Room #@RoomId</h3>
|
||||
|
||||
@if (loading)
|
||||
{
|
||||
<div class="spinner-border" role="status"></div>
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(error))
|
||||
{
|
||||
<div class="alert alert-danger">@error</div>
|
||||
}
|
||||
else if (roomData == null)
|
||||
{
|
||||
<div class="alert alert-warning">Room not found.</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">Room Info</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Room ID</label>
|
||||
<input type="number" class="form-control" @bind="editRoomId" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Name</label>
|
||||
<input type="text" class="form-control" @bind="editName" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Description</label>
|
||||
<textarea class="form-control" rows="3" @bind="editDescription"></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Image Name</label>
|
||||
<input type="text" class="form-control" @bind="editImageName" />
|
||||
@if (!string.IsNullOrEmpty(editImageName))
|
||||
{
|
||||
<img src="@Img.GetUrl(editImageName)" alt="preview" class="mt-2 img-thumbnail" style="max-width: 200px;" onerror="this.style.display='none'" />
|
||||
}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Creator Account ID</label>
|
||||
<input type="number" class="form-control" @bind="editCreatorAccountId" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Tags (JSON)</label>
|
||||
<textarea class="form-control font-monospace" rows="3" @bind="editTags"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">Settings</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">State</label>
|
||||
<input type="number" class="form-control" @bind="editState" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Accessibility</label>
|
||||
<input type="number" class="form-control" @bind="editAccessibility" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Min Level</label>
|
||||
<input type="number" class="form-control" @bind="editMinLevel" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Warning Mask</label>
|
||||
<input type="number" class="form-control" @bind="editWarningMask" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Custom Warning</label>
|
||||
<input type="text" class="form-control" @bind="editCustomWarning" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">Flags</div>
|
||||
<div class="card-body">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" @bind="editIsDorm" />
|
||||
<label class="form-check-label">Is Dorm</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" @bind="editIsRRO" />
|
||||
<label class="form-check-label">Is RRO</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" @bind="editCloningAllowed" />
|
||||
<label class="form-check-label">Cloning Allowed</label>
|
||||
</div>
|
||||
<hr />
|
||||
<h6>Platform Support</h6>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" @bind="editSupportsVRLow" />
|
||||
<label class="form-check-label">VR Low</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" @bind="editSupportsQuest2" />
|
||||
<label class="form-check-label">Quest 2</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" @bind="editSupportsMobile" />
|
||||
<label class="form-check-label">Mobile</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" @bind="editSupportsScreens" />
|
||||
<label class="form-check-label">Screens</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" @bind="editSupportsWalkVR" />
|
||||
<label class="form-check-label">Walk VR</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" @bind="editSupportsTeleportVR" />
|
||||
<label class="form-check-label">Teleport VR</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" @bind="editSupportsJuniors" />
|
||||
<label class="form-check-label">Supports Juniors</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (roomData.SubRooms?.Count > 0)
|
||||
{
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">SubRooms (@roomData.SubRooms.Count)</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr><th>ID</th><th>SubRoomId</th><th>Name</th><th>Max Players</th><th>Is Sandbox</th><th>Accessibility</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var sub in roomData.SubRooms)
|
||||
{
|
||||
<tr>
|
||||
<td>@sub.Id</td>
|
||||
<td>@sub.SubRoomId</td>
|
||||
<td>@sub.Name</td>
|
||||
<td>@sub.MaxPlayers</td>
|
||||
<td>@sub.IsSandbox</td>
|
||||
<td>@sub.Accessibility</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (roomData.LoadScreens?.Count > 0)
|
||||
{
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">Load Screens (@roomData.LoadScreens.Count)</div>
|
||||
<div class="card-body">
|
||||
@foreach (var ls in roomData.LoadScreens)
|
||||
{
|
||||
<div>@ls.ImageUrl - @ls.Tooltip</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="d-flex gap-2 mb-3">
|
||||
<button type="submit" class="btn btn-success" @onclick="SaveChanges" disabled="@saving">
|
||||
@(saving ? "Saving..." : "Save Changes")
|
||||
</button>
|
||||
<button type="button" class="btn btn-danger" @onclick="DeleteRoom" disabled="@deleting">
|
||||
@(deleting ? "Deleting..." : "Delete Room")
|
||||
</button>
|
||||
<a href="rooms/search" class="btn btn-outline-secondary">Back to Search</a>
|
||||
</div>
|
||||
|
||||
@if (!string.IsNullOrEmpty(saveMessage))
|
||||
{
|
||||
<div class="alert mt-2 @(saveMessage.Contains("Error") || saveMessage.Contains("Failed") ? "alert-danger" : "alert-success")">@saveMessage</div>
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter] public int RoomId { get; set; }
|
||||
|
||||
private bool loading = true;
|
||||
private bool saving;
|
||||
private bool deleting;
|
||||
private string? error;
|
||||
private string? saveMessage;
|
||||
private RoomDetails? roomData;
|
||||
|
||||
private int editRoomId;
|
||||
private string editName = "";
|
||||
private string editDescription = "";
|
||||
private string editImageName = "";
|
||||
private int editCreatorAccountId;
|
||||
private string editTags = "[]";
|
||||
private int editState;
|
||||
private int editAccessibility;
|
||||
private int editMinLevel;
|
||||
private int editWarningMask;
|
||||
private string? editCustomWarning;
|
||||
private bool editIsDorm;
|
||||
private bool editIsRRO;
|
||||
private bool editCloningAllowed;
|
||||
private bool editSupportsVRLow;
|
||||
private bool editSupportsQuest2;
|
||||
private bool editSupportsMobile;
|
||||
private bool editSupportsScreens;
|
||||
private bool editSupportsWalkVR;
|
||||
private bool editSupportsTeleportVR;
|
||||
private bool editSupportsJuniors;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadRoom();
|
||||
}
|
||||
|
||||
private async Task LoadRoom()
|
||||
{
|
||||
loading = true;
|
||||
error = null;
|
||||
try
|
||||
{
|
||||
roomData = await Http.GetFromJsonAsync<RoomDetails>($"/admin/api/rooms/{RoomId}");
|
||||
|
||||
if (roomData?.Room != null)
|
||||
{
|
||||
var r = roomData.Room;
|
||||
editRoomId = r.RoomId;
|
||||
editName = r.Name;
|
||||
editDescription = r.Description;
|
||||
editImageName = r.ImageName ?? "";
|
||||
editCreatorAccountId = r.CreatorAccountId;
|
||||
editTags = r.Tags ?? "[]";
|
||||
editState = r.State;
|
||||
editAccessibility = r.Accessibility;
|
||||
editMinLevel = r.MinLevel;
|
||||
editWarningMask = r.WarningMask;
|
||||
editCustomWarning = r.CustomWarning;
|
||||
editIsDorm = r.IsDorm;
|
||||
editIsRRO = r.IsRRO;
|
||||
editCloningAllowed = r.CloningAllowed;
|
||||
editSupportsVRLow = r.SupportsVRLow;
|
||||
editSupportsQuest2 = r.SupportsQuest2;
|
||||
editSupportsMobile = r.SupportsMobile;
|
||||
editSupportsScreens = r.SupportsScreens;
|
||||
editSupportsWalkVR = r.SupportsWalkVR;
|
||||
editSupportsTeleportVR = r.SupportsTeleportVR;
|
||||
editSupportsJuniors = r.SupportsJuniors;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = $"Failed to load room: {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveChanges()
|
||||
{
|
||||
saving = true;
|
||||
saveMessage = null;
|
||||
try
|
||||
{
|
||||
var payload = new
|
||||
{
|
||||
name = editName,
|
||||
description = editDescription,
|
||||
imageName = editImageName,
|
||||
state = editState,
|
||||
accessibility = editAccessibility,
|
||||
isDorm = editIsDorm,
|
||||
isRRO = editIsRRO,
|
||||
cloningAllowed = editCloningAllowed,
|
||||
minLevel = editMinLevel,
|
||||
warningMask = editWarningMask,
|
||||
customWarning = editCustomWarning,
|
||||
supportsVRLow = editSupportsVRLow,
|
||||
supportsQuest2 = editSupportsQuest2,
|
||||
supportsMobile = editSupportsMobile,
|
||||
supportsScreens = editSupportsScreens,
|
||||
supportsWalkVR = editSupportsWalkVR,
|
||||
supportsTeleportVR = editSupportsTeleportVR,
|
||||
supportsJuniors = editSupportsJuniors,
|
||||
tags = editTags
|
||||
};
|
||||
var response = await Http.PutAsJsonAsync($"/admin/api/rooms/{RoomId}", payload);
|
||||
response.EnsureSuccessStatusCode();
|
||||
saveMessage = "Room updated successfully!";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
saveMessage = $"Error: {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
saving = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteRoom()
|
||||
{
|
||||
if (!await ConfirmDelete()) return;
|
||||
deleting = true;
|
||||
saveMessage = null;
|
||||
try
|
||||
{
|
||||
var response = await Http.DeleteAsync($"/admin/api/rooms/{RoomId}");
|
||||
response.EnsureSuccessStatusCode();
|
||||
saveMessage = "Room deleted successfully!";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
saveMessage = $"Error: {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
deleting = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> ConfirmDelete()
|
||||
{
|
||||
var confirmed = await JSRuntime.InvokeAsync<bool>("confirm", $"Are you sure you want to delete room #{RoomId} and all its subrooms, load screens, promotions, and roles? This cannot be undone.");
|
||||
return confirmed;
|
||||
}
|
||||
|
||||
[Inject] private IJSRuntime JSRuntime { get; set; } = default!;
|
||||
[Inject] private HttpClient Http { get; set; } = default!;
|
||||
|
||||
private class RoomDetails
|
||||
{
|
||||
public Room? Room { get; set; }
|
||||
public List<SubRoom>? SubRooms { get; set; }
|
||||
public List<LoadScreen>? LoadScreens { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
@page "/rooms/search"
|
||||
@rendermode InteractiveServer
|
||||
@inject ImageUrlService Img
|
||||
|
||||
<PageTitle>Search Rooms</PageTitle>
|
||||
|
||||
<h3>Search Rooms</h3>
|
||||
<p class="text-muted">Search for rooms by name, description, or room ID.</p>
|
||||
|
||||
<div class="input-group mb-3">
|
||||
<input type="text" class="form-control" placeholder="Search by name, description, or room ID..." @bind="searchQuery" @bind:event="oninput" />
|
||||
<button class="btn btn-primary" @onclick="DoSearch" disabled="@(string.IsNullOrWhiteSpace(searchQuery) || searching)">
|
||||
@(searching ? "Searching..." : "Search")
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (searching)
|
||||
{
|
||||
<div class="spinner-border" role="status"></div>
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(error))
|
||||
{
|
||||
<div class="alert alert-danger">@error</div>
|
||||
}
|
||||
else if (results != null && results.Count == 0)
|
||||
{
|
||||
<div class="alert alert-info">No rooms found matching "@searchQuery".</div>
|
||||
}
|
||||
else if (results != null)
|
||||
{
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Room ID</th>
|
||||
<th>Image</th>
|
||||
<th>Name</th>
|
||||
<th>Creator</th>
|
||||
<th>State</th>
|
||||
<th>Dorm</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var room in results)
|
||||
{
|
||||
<tr>
|
||||
<td>@room.RoomId</td>
|
||||
<td>
|
||||
@if (!string.IsNullOrEmpty(room.ImageName))
|
||||
{
|
||||
<img src="@Img.GetUrl(room.ImageName)" alt="room" style="width: 60px; height: 40px; object-fit: cover; border-radius: 4px;" onerror="this.style.display='none'" />
|
||||
}
|
||||
</td>
|
||||
<td>@room.Name</td>
|
||||
<td>@room.CreatorAccountId</td>
|
||||
<td>@room.State</td>
|
||||
<td>@(room.IsDorm ? "Yes" : "No")</td>
|
||||
<td>
|
||||
<a href="rooms/edit/@room.RoomId" class="btn btn-sm btn-outline-primary">Edit</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
[Inject] private HttpClient Http { get; set; } = default!;
|
||||
|
||||
private string searchQuery = "";
|
||||
private bool searching;
|
||||
private string? error;
|
||||
private List<Room>? results;
|
||||
|
||||
private async Task DoSearch()
|
||||
{
|
||||
searching = true;
|
||||
error = null;
|
||||
results = null;
|
||||
|
||||
try
|
||||
{
|
||||
results = await Http.GetFromJsonAsync<List<Room>>($"/admin/api/rooms/search?q={Uri.EscapeDataString(searchQuery)}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = $"Search failed: {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
searching = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user