mirror of
https://github.com/TomasiDeveloping/PlayerManagement.git
synced 2026-04-16 09:12:20 +00:00
Implement feedback page
This commit is contained in:
parent
479eef5a21
commit
bfa32ea279
@ -17,6 +17,7 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="9.0.0" />
|
||||
<PackageReference Include="Octokit" Version="14.0.0" />
|
||||
<PackageReference Include="Serilog" Version="4.1.0" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.0.0" />
|
||||
|
||||
31
Api/Controllers/v1/FeedbacksController.cs
Normal file
31
Api/Controllers/v1/FeedbacksController.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using Application.DataTransferObjects.Feedback;
|
||||
using Application.Interfaces;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Api.Controllers.v1
|
||||
{
|
||||
[Route("api/v{version:apiVersion}/[controller]")]
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize]
|
||||
public class FeedbacksController(ILogger<FeedbacksController> logger, IGitHubService gitHubService) : ControllerBase
|
||||
{
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<string>> PostFeedback([FromForm] FeedbackDto feedbackDto)
|
||||
{
|
||||
try
|
||||
{
|
||||
var issue = await gitHubService.CreateIssueAsync(feedbackDto);
|
||||
return Ok(new {url = issue.HtmlUrl});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError(e, e.Message);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,3 @@
|
||||
// Configure Serilog logger
|
||||
|
||||
using Api.Configurations;
|
||||
using Api.Middleware;
|
||||
using Application;
|
||||
@ -43,6 +41,11 @@ try
|
||||
.ValidateDataAnnotations()
|
||||
.ValidateOnStart();
|
||||
|
||||
builder.Services.AddOptions<GitHubSetting>()
|
||||
.BindConfiguration("GitHubSettings")
|
||||
.ValidateDataAnnotations()
|
||||
.ValidateOnStart();
|
||||
|
||||
var jwtSection = builder.Configuration.GetRequiredSection("Jwt");
|
||||
builder.Services.ConfigureAndAddAuthentication(jwtSection);
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
<Folder Include="DataTransferObjects\Alliance\" />
|
||||
<Folder Include="Classes\" />
|
||||
<Folder Include="DataTransferObjects\MarshalGuardParticipant\" />
|
||||
<Folder Include="DataTransferObjects\Feedback\" />
|
||||
<Folder Include="DataTransferObjects\ZombieSiegeParticipant\" />
|
||||
<Folder Include="DataTransferObjects\Rank\" />
|
||||
<Folder Include="DataTransferObjects\Note\" />
|
||||
|
||||
@ -41,6 +41,7 @@ public static class ApplicationDependencyInjection
|
||||
services.AddTransient<IEmailService, EmailService>();
|
||||
services.AddTransient<IExcelService, ExcelService>();
|
||||
services.AddTransient<IEncryptionService, EncryptionService>();
|
||||
services.AddTransient<IGitHubService, GitHubService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
18
Application/DataTransferObjects/Feedback/FeedbackDto.cs
Normal file
18
Application/DataTransferObjects/Feedback/FeedbackDto.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Application.DataTransferObjects.Feedback;
|
||||
|
||||
public class FeedbackDto
|
||||
{
|
||||
public required string Type { get; set; }
|
||||
public required string Title { get; set; }
|
||||
public required string Description { get; set; }
|
||||
public string? ExpectedBehavior { get; set; }
|
||||
public string? ActualBehavior { get; set; }
|
||||
public string? Reproduction { get; set; }
|
||||
public string? Severity { get; set; }
|
||||
public string? Os { get; set; }
|
||||
public required string AppVersion { get; set; }
|
||||
public string? Email { get; set; }
|
||||
public IFormFile? Screenshot { get; set; }
|
||||
}
|
||||
9
Application/Interfaces/IGitHubService.cs
Normal file
9
Application/Interfaces/IGitHubService.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using Application.DataTransferObjects.Feedback;
|
||||
using Octokit;
|
||||
|
||||
namespace Application.Interfaces;
|
||||
|
||||
public interface IGitHubService
|
||||
{
|
||||
public Task<Issue> CreateIssueAsync(FeedbackDto feedbackDto);
|
||||
}
|
||||
@ -151,7 +151,8 @@ public class PlayerRepository(ApplicationContext context, IMapper mapper, ILogge
|
||||
ModifiedBy = null,
|
||||
DismissalReason = null,
|
||||
DismissedAt = null,
|
||||
IsDismissed = false
|
||||
IsDismissed = false,
|
||||
Id = Guid.CreateVersion7()
|
||||
};
|
||||
|
||||
await context.Players.AddAsync(newPlayer, cancellationToken);
|
||||
|
||||
70
Application/Services/GitHubService.cs
Normal file
70
Application/Services/GitHubService.cs
Normal file
@ -0,0 +1,70 @@
|
||||
using Application.DataTransferObjects.Feedback;
|
||||
using Application.Interfaces;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Octokit;
|
||||
using Utilities.Classes;
|
||||
using ProductHeaderValue = Octokit.ProductHeaderValue;
|
||||
|
||||
namespace Application.Services;
|
||||
|
||||
public class GitHubService(IOptions<GitHubSetting> gitHubSettingOption) : IGitHubService
|
||||
{
|
||||
private readonly GitHubSetting _gitHubSetting = gitHubSettingOption.Value;
|
||||
|
||||
public async Task<Issue> CreateIssueAsync(FeedbackDto feedbackDto)
|
||||
{
|
||||
var gitHubClient = new GitHubClient(new ProductHeaderValue("PlayerManagerApi"))
|
||||
{
|
||||
Credentials = new Credentials(_gitHubSetting.Token)
|
||||
};
|
||||
|
||||
var label = feedbackDto.Type == "bug" ? "bug" : "enhancement";
|
||||
var issueBody = $"**Description:**\n{feedbackDto.Description}\n\n";
|
||||
|
||||
if (label == "bug")
|
||||
{
|
||||
issueBody += $"**Expected Behavior:**\n{feedbackDto.ExpectedBehavior}\n\n" +
|
||||
$"**Actual Behavior:**\n{feedbackDto.ActualBehavior}\n\n" +
|
||||
$"**Steps to Reproduce:**\n{feedbackDto.Reproduction}\n\n" +
|
||||
$"**Severity:** {feedbackDto.Severity}\n\n" +
|
||||
$"**Operating System:** {feedbackDto.Os}\n\n" +
|
||||
$"**App Version:** {feedbackDto.AppVersion}\n\n";
|
||||
}
|
||||
|
||||
if (feedbackDto.Email is not null)
|
||||
{
|
||||
issueBody += $"**Contact:** {feedbackDto.Email}\n\n";
|
||||
}
|
||||
|
||||
if (feedbackDto.Screenshot is { Length: > 0 })
|
||||
{
|
||||
using var stream = new MemoryStream();
|
||||
await feedbackDto.Screenshot.CopyToAsync(stream);
|
||||
var fileBytes = stream.ToArray();
|
||||
|
||||
var filePath = $"screenshots/{Guid.NewGuid()}.png";
|
||||
var createFileRequest = new CreateFileRequest(
|
||||
"Upload Screenshot",
|
||||
Convert.ToBase64String(fileBytes),
|
||||
false
|
||||
);
|
||||
|
||||
var fileResponse = await gitHubClient.Repository.Content.CreateFile(
|
||||
_gitHubSetting.Owner,
|
||||
_gitHubSetting.Name,
|
||||
filePath,
|
||||
createFileRequest
|
||||
);
|
||||
|
||||
issueBody += $"**Screenshot:**\n\n ";
|
||||
}
|
||||
|
||||
var createIssue = new NewIssue($"[{feedbackDto.Type.ToUpper()}] {feedbackDto.Title}")
|
||||
{
|
||||
Body = issueBody
|
||||
};
|
||||
createIssue.Labels.Add(label);
|
||||
|
||||
return await gitHubClient.Issue.Create(_gitHubSetting.Owner, _gitHubSetting.Name, createIssue);
|
||||
}
|
||||
}
|
||||
@ -17,6 +17,15 @@ All notable changes to this project are documented here.
|
||||
This project is currently in the **Beta Phase**.
|
||||
|
||||
---
|
||||
### **[0.8.0]** - *2025-03-11*
|
||||
#### ✨ Added
|
||||
- **Feedback Page:** Users can now submit feedback, including bug reports and feature requests.
|
||||
- **GitHub Integration:** Feedback is automatically created as a GitHub issue, providing a direct link to track progress.
|
||||
- **Screenshot Upload:** Users can attach a screenshot to better illustrate issues.
|
||||
- **Success Message:** After submission, the form hides, and a success message with the GitHub issue link is displayed.
|
||||
|
||||
🛠️ **Fixed**
|
||||
- (N/A)
|
||||
|
||||
### **[0.7.0]** - *2025-02-06*
|
||||
#### ✨ Added
|
||||
|
||||
@ -24,6 +24,7 @@ import {ZombieSiegeDetailComponent} from "./pages/zombie-siege/zombie-siege-deta
|
||||
import {CustomEventDetailComponent} from "./pages/custom-event/custom-event-detail/custom-event-detail.component";
|
||||
import {DismissPlayerComponent} from "./pages/dismiss-player/dismiss-player.component";
|
||||
import {MvpComponent} from "./pages/mvp/mvp.component";
|
||||
import {FeedbackComponent} from "./pages/feedback/feedback.component";
|
||||
|
||||
const routes: Routes = [
|
||||
{path: 'players', component: PlayerComponent, canActivate: [authGuard]},
|
||||
@ -40,6 +41,7 @@ const routes: Routes = [
|
||||
{ path: 'alliance', component: AllianceComponent, canActivate: [authGuard]},
|
||||
{path: 'account', component: AccountComponent, canActivate: [authGuard]},
|
||||
{path: 'change-password', component: ChangePasswordComponent, canActivate: [authGuard]},
|
||||
{path: 'feedback', component: FeedbackComponent, canActivate: [authGuard]},
|
||||
{path: 'custom-event', component: CustomEventComponent, canActivate: [authGuard]},
|
||||
{path: 'custom-event-detail/:id', component: CustomEventDetailComponent, canActivate: [authGuard]},
|
||||
{path: 'zombie-siege', component: ZombieSiegeComponent, canActivate: [authGuard]},
|
||||
|
||||
@ -59,6 +59,7 @@ import {PlayerInfoVsDuelComponent} from "./pages/player-information/player-info-
|
||||
import { MvpComponent } from './pages/mvp/mvp.component';
|
||||
import { AllianceApiKeyComponent } from './pages/alliance/alliance-api-key/alliance-api-key.component';
|
||||
import { AllianceUserAdministrationComponent } from './pages/alliance/alliance-user-administration/alliance-user-administration.component';
|
||||
import { FeedbackComponent } from './pages/feedback/feedback.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@ -107,7 +108,8 @@ import { AllianceUserAdministrationComponent } from './pages/alliance/alliance-u
|
||||
PlayerExcelImportModalComponent,
|
||||
MvpComponent,
|
||||
AllianceApiKeyComponent,
|
||||
AllianceUserAdministrationComponent
|
||||
AllianceUserAdministrationComponent,
|
||||
FeedbackComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
|
||||
@ -78,6 +78,7 @@
|
||||
<div ngbDropdownMenu>
|
||||
<button (click)="isShown = false" routerLink="/account" ngbDropdownItem>Account</button>
|
||||
<button (click)="isShown = false" routerLink="/change-password" ngbDropdownItem>Change password</button>
|
||||
<button (click)="isShown = false" routerLink="/feedback" ngbDropdownItem>Submit Feedback</button>
|
||||
<div class="dropdown-divider"></div>
|
||||
<button (click)="onLogout()" ngbDropdownItem>Logout</button>
|
||||
</div>
|
||||
|
||||
0
Ui/src/app/pages/feedback/feedback.component.css
Normal file
0
Ui/src/app/pages/feedback/feedback.component.css
Normal file
164
Ui/src/app/pages/feedback/feedback.component.html
Normal file
164
Ui/src/app/pages/feedback/feedback.component.html
Normal file
@ -0,0 +1,164 @@
|
||||
<div class="container mt-3 pb-5">
|
||||
<h2 class="text-center">Submit Feedback</h2>
|
||||
|
||||
@if (!isSubmitted) {
|
||||
<!-- Feedback Form -->
|
||||
<form [formGroup]="feedbackForm">
|
||||
<div class="form-floating mb-3 is-invalid">
|
||||
<select [ngClass]="{
|
||||
'is-valid': f['type'].valid,
|
||||
'is-invalid': f['type'].invalid && (f['type'].touched || f['type'].dirty)
|
||||
}" class="form-control" id="type" formControlName="type" (change)="onTypeChange($event)">
|
||||
<option value="bug">Bug Report</option>
|
||||
<option value="feature">Feature Request</option>
|
||||
</select>
|
||||
<label for="type">Feedback Type</label>
|
||||
</div>
|
||||
|
||||
<div class="form-floating mb-3 is-invalid">
|
||||
<input [ngClass]="{
|
||||
'is-valid': f['title'].valid,
|
||||
'is-invalid': f['title'].invalid && (f['title'].touched || f['title'].dirty)
|
||||
}" type="text" class="form-control" id="title" placeholder="Title" formControlName="title">
|
||||
<label for="title">Title</label>
|
||||
@if (f['title'].invalid && (f['title'].touched || f['title'].dirty)) {
|
||||
<div class="invalid-feedback">
|
||||
@if (f['title'].hasError('required')) {
|
||||
<p>Title is required</p>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="form-floating mb-3 is-invalid">
|
||||
<textarea [ngClass]="{
|
||||
'is-valid': f['description'].valid,
|
||||
'is-invalid': f['description'].invalid && (f['description'].touched || f['description'].dirty)
|
||||
}" class="form-control" id="description" formControlName="description" placeholder="Describe the issue or feature" style="height: 100px;"></textarea>
|
||||
<label for="description">Description</label>
|
||||
@if (f['description'].invalid && (f['description'].touched || f['description'].dirty)) {
|
||||
<div class="invalid-feedback">
|
||||
@if (f['description'].hasError('required')) {
|
||||
<p>Description is required</p>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="form-floating mb-3 is-invalid">
|
||||
<input type="email" class="form-control" id="email" formControlName="email" placeholder="Enter your email (optional)">
|
||||
<label for="email">Your Email (Optional)</label>
|
||||
</div>
|
||||
|
||||
@if (feedbackType === 'bug') {
|
||||
<div class="form-floating mb-3 is-invalid">
|
||||
<textarea [ngClass]="{
|
||||
'is-valid': f['expectedBehavior'].valid,
|
||||
'is-invalid': f['expectedBehavior'].invalid && (f['expectedBehavior'].touched || f['expectedBehavior'].dirty)
|
||||
}" class="form-control" id="expectedBehavior" formControlName="expectedBehavior" placeholder="What did you expect to happen?" style="height: 100px;"></textarea>
|
||||
<label for="expectedBehavior">Expected Behavior</label>
|
||||
@if (f['expectedBehavior'].invalid && (f['expectedBehavior'].touched || f['expectedBehavior'].dirty)) {
|
||||
<div class="invalid-feedback">
|
||||
@if (f['expectedBehavior'].hasError('required')) {
|
||||
<p>ExpectedBehavior is required</p>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="form-floating mb-3 is-invalid">
|
||||
<textarea [ngClass]="{
|
||||
'is-valid': f['actualBehavior'].valid,
|
||||
'is-invalid': f['actualBehavior'].invalid && (f['actualBehavior'].touched || f['actualBehavior'].dirty)
|
||||
}" class="form-control" id="actualBehavior" formControlName="actualBehavior" placeholder="What actually happened?" style="height: 100px;"></textarea>
|
||||
<label for="actualBehavior">Actual Behavior</label>
|
||||
@if (f['actualBehavior'].invalid && (f['actualBehavior'].touched || f['actualBehavior'].dirty)) {
|
||||
<div class="invalid-feedback">
|
||||
@if (f['actualBehavior'].hasError('required')) {
|
||||
<p>ActualBehavior is required</p>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="form-floating mb-3 is-invalid">
|
||||
<textarea [ngClass]="{
|
||||
'is-valid': f['reproduction'].valid,
|
||||
'is-invalid': f['reproduction'].invalid && (f['reproduction'].touched || f['reproduction'].dirty)
|
||||
}" class="form-control" id="reproduction" formControlName="reproduction" placeholder="List the steps to reproduce the issue" style="height: 100px;"></textarea>
|
||||
<label for="reproduction">Steps to Reproduce</label>
|
||||
@if (f['reproduction'].invalid && (f['reproduction'].touched || f['reproduction'].dirty)) {
|
||||
<div class="invalid-feedback">
|
||||
@if (f['reproduction'].hasError('required')) {
|
||||
<p>Reproduction is required</p>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="form-floating mb-3 is-invalid">
|
||||
<select [ngClass]="{
|
||||
'is-valid': f['severity'].valid,
|
||||
'is-invalid': f['severity'].invalid && (f['severity'].touched || f['severity'].dirty)
|
||||
}" class="form-control" id="severity" formControlName="severity">
|
||||
<option value="low">Low</option>
|
||||
<option value="medium">Medium</option>
|
||||
<option value="high">High</option>
|
||||
</select>
|
||||
<label for="severity">Severity</label>
|
||||
</div>
|
||||
|
||||
<div class="form-floating mb-3 is-invalid">
|
||||
<select [ngClass]="{
|
||||
'is-valid': f['os'].valid,
|
||||
'is-invalid': f['os'].invalid && (f['os'].touched || f['os'].dirty)
|
||||
}" class="form-control" id="os" formControlName="os">
|
||||
<option value="windows">Windows</option>
|
||||
<option value="macos">macOS</option>
|
||||
<option value="linux">Linux</option>
|
||||
<option value="android">Android</option>
|
||||
<option value="ios">iOS</option>
|
||||
</select>
|
||||
<label for="os">Operating System</label>
|
||||
@if (f['os'].invalid && (f['os'].touched || f['os'].dirty)) {
|
||||
<div class="invalid-feedback">
|
||||
@if (f['os'].hasError('required')) {
|
||||
<p>Operating System is required</p>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="form-floating mb-3 is-invalid">
|
||||
<input [ngClass]="{
|
||||
'is-valid': f['appVersion'].valid,
|
||||
'is-invalid': f['appVersion'].invalid && (f['appVersion'].touched || f['appVersion'].dirty)
|
||||
}" type="text" class="form-control" id="appVersion" formControlName="appVersion" placeholder="App version (e.g., 1.2.3)" readonly>
|
||||
<label for="appVersion">App Version</label>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="formFile" class="form-label">Screenshot (optional):</label>
|
||||
<input class="form-control" type="file" id="formFile" (change)="onFileChange($event)">
|
||||
</div>
|
||||
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-between">
|
||||
<button (click)="onCancel()" type="button" class="btn btn-warning">Cancel</button>
|
||||
<button [disabled]="feedbackForm.invalid" (click)="onSubmit()" type="submit" class="btn btn-success">Send feedback</button>
|
||||
</div>
|
||||
</form>
|
||||
} @else {
|
||||
<div class="alert alert-success text-center">
|
||||
<h3>Thank you for your feedback!</h3>
|
||||
<p>Your feedback has been successfully submitted.</p>
|
||||
<p>
|
||||
You can track the progress of your feedback <br />
|
||||
<a [href]="issueUrl" target="_blank" class="btn btn-success mt-2">View Issue on GitHub</a>
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
</div>
|
||||
101
Ui/src/app/pages/feedback/feedback.component.ts
Normal file
101
Ui/src/app/pages/feedback/feedback.component.ts
Normal file
@ -0,0 +1,101 @@
|
||||
import {Component, inject, OnInit} from '@angular/core';
|
||||
import {FormBuilder, FormGroup, Validators} from "@angular/forms";
|
||||
import {environment} from "../../../environments/environment";
|
||||
import {FeedbackService} from "../../services/feedback.service";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
import {Router} from "@angular/router";
|
||||
|
||||
@Component({
|
||||
selector: 'app-feedback',
|
||||
templateUrl: './feedback.component.html',
|
||||
styleUrl: './feedback.component.css'
|
||||
})
|
||||
export class FeedbackComponent implements OnInit {
|
||||
|
||||
private readonly _fb: FormBuilder = inject(FormBuilder);
|
||||
private readonly _feedbackService: FeedbackService = inject(FeedbackService);
|
||||
private readonly _toastr: ToastrService = inject(ToastrService);
|
||||
private readonly _router: Router = inject(Router);
|
||||
|
||||
public feedbackForm!: FormGroup;
|
||||
public feedbackType: string = 'feature';
|
||||
public isSubmitted: boolean = false;
|
||||
public issueUrl: string = '';
|
||||
|
||||
private appVersion: string = environment.version;
|
||||
|
||||
get f() {
|
||||
return this.feedbackForm.controls;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.createForm();
|
||||
}
|
||||
|
||||
createForm() {
|
||||
this.feedbackForm = this._fb.group({
|
||||
type: [this.feedbackType, Validators.required],
|
||||
title: ['', Validators.required],
|
||||
description: ['', Validators.required],
|
||||
appVersion: [this.appVersion],
|
||||
email: ['', Validators.email],
|
||||
screenshot: [null]
|
||||
});
|
||||
}
|
||||
|
||||
onTypeChange(event: any) {
|
||||
const type = event.target.value;
|
||||
this.feedbackType = type;
|
||||
if (type === 'bug') {
|
||||
this.feedbackForm.addControl('expectedBehavior', this._fb.control('', Validators.required));
|
||||
this.feedbackForm.addControl('actualBehavior', this._fb.control('', Validators.required));
|
||||
this.feedbackForm.addControl('reproduction', this._fb.control('', Validators.required));
|
||||
this.feedbackForm.addControl('severity', this._fb.control('medium', Validators.required));
|
||||
this.feedbackForm.addControl('os', this._fb.control('', Validators.required));
|
||||
} else {
|
||||
this.feedbackForm.removeControl('expectedBehavior');
|
||||
this.feedbackForm.removeControl('actualBehavior');
|
||||
this.feedbackForm.removeControl('reproduction');
|
||||
this.feedbackForm.removeControl('severity');
|
||||
this.feedbackForm.removeControl('os');
|
||||
}
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
if (this.feedbackForm.valid) {
|
||||
const formData = new FormData();
|
||||
Object.keys(this.feedbackForm.value).forEach((key) => {
|
||||
formData.append(key, this.feedbackForm.value[key]);
|
||||
});
|
||||
|
||||
if (this.feedbackForm.get('screenshot')?.value) {
|
||||
formData.append('screenshot', this.feedbackForm.get('screenshot')?.value);
|
||||
}
|
||||
|
||||
this._feedbackService.submitFeedback(formData).subscribe({
|
||||
next: ((response) => {
|
||||
if (response) {
|
||||
this.isSubmitted = true;
|
||||
this.issueUrl = response.url;
|
||||
} else {
|
||||
this._toastr.error('Failure submitted feedback', 'Feedback');
|
||||
}
|
||||
}),
|
||||
error: (error) => {
|
||||
console.log(error);
|
||||
this._toastr.error('Failure submitted feedback', 'Feedback');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onFileChange(event: any) {
|
||||
if (event.target.files && event.target.files.length > 0) {
|
||||
this.feedbackForm.patchValue({ screenshot: event.target.files[0] });
|
||||
}
|
||||
}
|
||||
|
||||
onCancel() {
|
||||
this._router.navigate(['/']).then();
|
||||
}
|
||||
}
|
||||
18
Ui/src/app/services/feedback.service.ts
Normal file
18
Ui/src/app/services/feedback.service.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import {inject, Injectable} from '@angular/core';
|
||||
import {environment} from "../../environments/environment";
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {Observable} from "rxjs";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class FeedbackService {
|
||||
|
||||
private readonly _serviceUrl = environment.apiBaseUrl + 'Feedbacks/';
|
||||
private readonly _httpClient: HttpClient = inject(HttpClient);
|
||||
|
||||
public submitFeedback(formData: FormData): Observable<{url:string}> {
|
||||
return this._httpClient.post<{url:string}>(this._serviceUrl, formData);
|
||||
}
|
||||
|
||||
}
|
||||
15
Utilities/Classes/GitHubSetting.cs
Normal file
15
Utilities/Classes/GitHubSetting.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Utilities.Classes;
|
||||
|
||||
public class GitHubSetting
|
||||
{
|
||||
[Required]
|
||||
public required string Owner { get; set; }
|
||||
|
||||
[Required]
|
||||
public required string Name { get; set; }
|
||||
|
||||
[Required]
|
||||
public required string Token { get; set; }
|
||||
}
|
||||
@ -8,7 +8,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Constants\" />
|
||||
<Folder Include="Classes\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -16,6 +15,7 @@
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Features" Version="5.0.17" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="9.0.0" />
|
||||
<PackageReference Include="Octokit" Version="14.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user