Skip to content

Commit

Permalink
adding ability to submit support tickets in-app.
Browse files Browse the repository at this point in the history
added logos to the patient profile menu
added link to fundraising doc in-app
update fontawesome version.

fixes #272
  • Loading branch information
AnalogJ committed Oct 9, 2023
1 parent bbf5169 commit cae3afc
Show file tree
Hide file tree
Showing 10 changed files with 289 additions and 9 deletions.
14 changes: 14 additions & 0 deletions backend/pkg/models/support_request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package models

type SupportRequest struct {
FullName string `json:"full_name"`
Email string `json:"email"`
RequestContent string `json:"request_content"`

CurrentPage string `json:"current_page"`
DistType string `json:"dist_type"`
Version string `json:"version"`
Flavor string `json:"flavor"`
Os string `json:"os"`
Arch string `json:"arch"`
}
58 changes: 58 additions & 0 deletions backend/pkg/web/handler/support.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package handler

import (
"fmt"
"github.com/fastenhealth/fasten-onprem/backend/pkg/models"
"github.com/gin-gonic/gin"
"io/ioutil"
"net/http"
"net/url"
)

func SupportRequest(c *gin.Context) {

var supportRequest models.SupportRequest
if err := c.ShouldBindJSON(&supportRequest); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
return
}

//submit support request to Google Form
//https://medium.com/front-end-augustus-study-notes/custom-google-form-en-f7be4c27a98b

//source: https://docs.google.com/forms/d/e/1FAIpQLSfFxttuzE4mYNtQxa2XxsHw3uyNsxUzE-BeYF4JXxoKku3R5A/viewform
formUrl := "https://docs.google.com/forms/u/0/d/e/1FAIpQLSfFxttuzE4mYNtQxa2XxsHw3uyNsxUzE-BeYF4JXxoKku3R5A/formResponse"

supportRequestResponse, err := http.PostForm(formUrl, url.Values{
"entry.1688458216": {supportRequest.FullName},
"entry.153181769": {supportRequest.Email},
"entry.1194157548": {supportRequest.RequestContent},
"entry.108410483": {supportRequest.CurrentPage},
"entry.1640090028": {supportRequest.DistType},
"entry.882116507": {supportRequest.Flavor},
"entry.1331679697": {supportRequest.Version},
"entry.164864077": {supportRequest.Arch},
"entry.1469583108": {supportRequest.Os},
})
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
return
}

defer supportRequestResponse.Body.Close()
body, err := ioutil.ReadAll(supportRequestResponse.Body)

if err != nil {
//handle read response error
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
return
} else if supportRequestResponse.StatusCode != 200 {
//handle non 200 response
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": fmt.Sprintf("status code error: %d %s", supportRequestResponse.StatusCode, supportRequestResponse.Status)})
return
}

fmt.Printf("%s\n", string(body))

c.JSON(http.StatusOK, gin.H{"success": true})
}
1 change: 1 addition & 0 deletions backend/pkg/web/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ func (ae *AppEngine) Setup() (*gin.RouterGroup, *gin.Engine) {
//r.GET("/cors/*proxyPath", handler.CORSProxy)
//r.OPTIONS("/cors/*proxyPath", handler.CORSProxy)
api.GET("/glossary/code", handler.GlossarySearchByCode)
api.POST("/support/request", handler.SupportRequest)

secure := api.Group("/secure").Use(middleware.RequireAuth())
{
Expand Down
115 changes: 113 additions & 2 deletions frontend/src/app/components/header/header.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,121 @@ <h6>{{current_user_claims.full_name || current_user_claims.sub || current_user_c
<span>Adminstrator</span>
</div><!-- az-header-profile -->

<a ngbTooltip="not yet implemented" class="dropdown-item"><i class="typcn typcn-time"></i> Activity Logs</a>
<a (click)="signOut($event)" class="dropdown-item"><i class="typcn typcn-power-outline"></i> Sign Out</a>
<a (click)="openSupportForm(content)" class="dropdown-item cursor-pointer"><i style="font-size: medium;" class="fas fa-question-circle"></i> Get Support</a>
<a class="dropdown-item cursor-pointer" href="https://docs.fastenhealth.com/FUNDRAISING.html" externalLink><i style="font-size: medium;" class="fas fa-hand-holding-medical"></i> Donate</a>
<a (click)="signOut($event)" class="dropdown-item cursor-pointer"><i style="font-size: medium;" class="fas fa-power-off"></i> Sign Out</a>
</div><!-- dropdown-menu -->
</div>
</div><!-- az-header-right -->
</div><!-- container -->
</div><!-- az-header -->


<ng-template #content let-modal>
<div class="modal-header">
<h4 class="modal-title">Get Support</h4>
<button type="button" class="btn close" aria-label="Close" (click)="modal.dismiss('Cross click')"><span aria-hidden="true">×</span></button>
</div>
<div class="modal-body">
<ngb-accordion #acc="ngbAccordion" activeIds="ngb-panel-0">
<ngb-panel title="File an Issue">
<ng-template ngbPanelContent>

<p>If you're having issues with Fasten Health, you can use this form to file a ticket.</p>
<div class="alert alert-warning" role="alert">
<strong>Your Privacy is Important</strong> None of the information provided in this form will be made public,
however we will anonymize & summarize the content before creating a <a href="https://github.com/fastenhealth/fasten-onprem/issues" externalLink><i class="fab fa-github"></i> Github Issue</a> for tracking purposes.
</div>

<form *ngIf="!submitSuccess; else supportRequestSuccess" (ngSubmit)="submitSupportForm()" #supportRequestForm="ngForm">

<div class="form-group">
<div class="row">
<div class="col-6">
<label class="az-content-label tx-11 tx-medium tx-gray-600">Name</label>
<input [(ngModel)]="newSupportRequest.full_name" name="full_name" #full_name="ngModel" required minlength="2" type="text" class="form-control" placeholder="Enter your name">

<div *ngIf="full_name.invalid && (full_name.dirty || full_name.touched)" class="alert alert-danger">
<div *ngIf="full_name.errors?.['required']">
Name is required.
</div>
<div *ngIf="full_name.errors?.['minlength']">
Name must be at least 2 characters long.
</div>
</div>
</div>
<div class="col-6">
<label class="az-content-label tx-11 tx-medium tx-gray-600">Email Address</label>
<input [(ngModel)]="newSupportRequest.email" email name="emailAddr" #emailAddr="ngModel" required minlength="4" type="text" class="form-control" autocapitalize="none" placeholder="Enter your email address">

<div *ngIf="emailAddr.invalid && (emailAddr.dirty || emailAddr.touched)" class="alert alert-danger">
<div *ngIf="emailAddr.errors?.['required']">
Email Address is required.
</div>
<div *ngIf="emailAddr.errors?.['minlength']">
Email Address must be at least 4 characters long.
</div>
<div *ngIf="emailAddr.errors?.['email']">
Email Address must be a valid email address.
</div>
</div>
</div>
</div>

</div><!-- form-group -->

<div class="form-group">
<label class="az-content-label tx-11 tx-medium tx-gray-600">Issue Description</label>
<textarea [(ngModel)]="newSupportRequest.request_content" name="requestContent" #requestContent="ngModel" rows="3" class="form-control" placeholder="Please provide a detailed description of the issue that you're encountering" required minlength="4"></textarea>

<div *ngIf="requestContent.invalid && (requestContent.dirty || requestContent.touched)" class="alert alert-danger">
<div *ngIf="requestContent.errors?.['required']">
Issue Description is required.
</div>
<div *ngIf="requestContent.errors?.['minlength']">
Issue Description must be at least 4 characters long.
</div>
</div>
</div><!-- form-group -->

<button [disabled]="!supportRequestForm.form.valid || loading" type="submit" class="btn btn-az-primary btn-block">Submit Issue</button>
<div *ngIf="errorMsg" class="alert alert-danger mt-3" role="alert">
<strong>Error</strong> {{errorMsg}}
</div>
</form>

<ng-template #supportRequestSuccess>
<div class="tx-center pd-y-20 pd-x-20">
<i class="far fa-check-square tx-100 tx-success lh-1 mg-t-20 d-inline-block"></i>
<h4 class="tx-success mg-b-20">Success!</h4>
<p class="mg-b-20 mg-x-20">Someone from the dev team will contact <br/>you shortly regarding your feedback. Thanks!</p>
<button type="button" (click)="resetSupportRequestForm()" class="btn btn-success pd-x-25">Continue</button>
</div>
</ng-template>

</ng-template>
</ngb-panel>
<ngb-panel title="Community Discussions & Support">
<ng-template ngbPanelContent>
<p>Whether you have a question or a suggestion, we are always happy to listen to you.</p>
<ul>
<li>
<a href="https://discord.gg/Bykz6BAN8p" externalLink><i class="fab fa-discord"></i> Discord Channel</a>
</li>
<li>
<a href="https://www.reddit.com/r/FastenHealth/" externalLink><i class="fab fa-reddit"></i> Reddit</a>
</li>
<li>
<a href="https://docs.fastenhealth.com/" externalLink><i class="fas fa-book"></i> Documentation</a>
</li>
</ul>


</ng-template>
</ngb-panel>
</ngb-accordion>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" (click)="modal.close('Close click')">Close</button>
</div>
</ng-template>
76 changes: 72 additions & 4 deletions frontend/src/app/components/header/header.component.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,37 @@
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import {Component, OnDestroy, OnInit} from '@angular/core';
import {ActivatedRoute, NavigationStart, Router} from '@angular/router';
import {AuthService} from '../../services/auth.service';
import {UserRegisteredClaims} from '../../models/fasten/user-registered-claims';
import {FastenApiService} from '../../services/fasten-api.service';
import {BackgroundJob} from '../../models/fasten/background-job';
import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
import {SupportRequest} from '../../models/fasten/support-request';
import {environment} from '../../../environments/environment';
import {versionInfo} from '../../../environments/versions';
import {Subscription} from 'rxjs';
import {ToastNotification, ToastType} from '../../models/fasten/toast';

@Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss']
})
export class HeaderComponent implements OnInit {
export class HeaderComponent implements OnInit, OnDestroy {
current_user_claims: UserRegisteredClaims
backgroundJobs: BackgroundJob[] = []
constructor(private authService: AuthService, private router: Router, private fastenApi: FastenApiService) { }

newSupportRequest: SupportRequest = null
loading: boolean = false
errorMsg: string = ""
submitSuccess: boolean = false

routerSubscription: Subscription = null

constructor(
private authService: AuthService,
private router: Router,
private fastenApi: FastenApiService,
private modalService: NgbModal) { }

ngOnInit() {
try {
Expand All @@ -21,11 +40,25 @@ export class HeaderComponent implements OnInit {
this.current_user_claims = new UserRegisteredClaims()
}


this.fastenApi.getBackgroundJobs().subscribe((data) => {
this.backgroundJobs = data.filter((job) => {
return job.data?.checkpoint_data?.summary?.UpdatedResources?.length > 0
})
})

this.resetSupportRequestForm()
this.routerSubscription = this.router.events.subscribe((event) => {
if (event instanceof NavigationStart) {
this.newSupportRequest.current_page = event.url.toString()
}
})
}

ngOnDestroy() {
if(this.routerSubscription){
this.routerSubscription.unsubscribe()
}
}

closeMenu(e) {
Expand All @@ -42,4 +75,39 @@ export class HeaderComponent implements OnInit {
this.authService.Logout()
.then(() => this.router.navigate(['auth/signin']))
}

//support Form

openSupportForm(content) {
this.modalService.open(content, { size: 'lg' });
}

resetSupportRequestForm() {
this.submitSuccess = false
let newSupportRequest = new SupportRequest()
newSupportRequest.dist_type = environment.environment_desktop ? 'desktop' : 'docker'
newSupportRequest.flavor = environment.environment_name
newSupportRequest.version = versionInfo.version
newSupportRequest.current_page = this.router.url.toString()

this.newSupportRequest = newSupportRequest
}
submitSupportForm() {
console.log("submitting support form", this.newSupportRequest)
this.loading = false

this.fastenApi.supportRequest(this.newSupportRequest).subscribe((resp: any) => {
this.loading = false
console.log(resp);
this.submitSuccess = true
//show success toast? close modal?
},
(err)=>{
this.loading = false
console.error("an error occurred during support request submission",err)
this.errorMsg = err || "An error occurred while submitting your support request. Please try again later."
})
}


}
3 changes: 2 additions & 1 deletion frontend/src/app/components/shared.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ import {ListOrganizationComponent} from './list-generic-resource/list-organizati
import {ListPractitionerComponent} from './list-generic-resource/list-practitioner.component'
import {ListProcedureComponent} from './list-generic-resource/list-procedure.component'
import {ListServiceRequestComponent} from './list-generic-resource/list-service-request.component';
import {NgbCollapseModule, NgbModule, NgbDropdownModule} from '@ng-bootstrap/ng-bootstrap';
import {NgbCollapseModule, NgbModule, NgbDropdownModule, NgbAccordionModule} from '@ng-bootstrap/ng-bootstrap';
import {PipesModule} from '../pipes/pipes.module';
import {ResourceListOutletDirective} from './resource-list/resource-list-outlet.directive';
import {DirectivesModule} from '../directives/directives.module';
Expand All @@ -91,6 +91,7 @@ import {DirectivesModule} from '../directives/directives.module';
NgbModule,
NgbDropdownModule,
NgbCollapseModule,
NgbAccordionModule,
FormsModule,
ReactiveFormsModule,
MomentModule,
Expand Down
13 changes: 13 additions & 0 deletions frontend/src/app/models/fasten/support-request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

export class SupportRequest {
full_name: string
email: string
request_content: string

current_page: string
dist_type: 'desktop' | 'docker' | 'cloud'
flavor: string
version: string
arch: string
os: string
}
14 changes: 14 additions & 0 deletions frontend/src/app/services/fasten-api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {DashboardWidgetQuery} from '../models/widget/dashboard-widget-query';
import {ResourceGraphResponse} from '../models/fasten/resource-graph-response';
import { fetchEventSource } from '@microsoft/fetch-event-source';
import {BackgroundJob} from '../models/fasten/background-job';
import {SupportRequest} from '../models/fasten/support-request';

@Injectable({
providedIn: 'root'
Expand Down Expand Up @@ -292,4 +293,17 @@ export class FastenApiService {
})
);
}


supportRequest(request: SupportRequest): Observable<any> {
return this._httpClient.post<any>(`${GetEndpointAbsolutePath(globalThis.location, environment.fasten_api_endpoint_base)}/support/request`, request)
.pipe(
map((response: ResponseWrapper) => {
console.log("Support request response", response)
// @ts-ignore
return {}
})
);
}

}
2 changes: 1 addition & 1 deletion frontend/src/index-cloud.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<title>fastenhealth</title>
<!-- Fonts and icons -->
<link href="https://fonts.googleapis.com/css?family=Poppins:200,300,400,600,700,800" rel="stylesheet"/>
<link href="https://use.fontawesome.com/releases/v5.0.6/css/all.css" rel="stylesheet"/>
<link href="https://use.fontawesome.com/releases/v5.15.4/css/all.css" rel="stylesheet"/>
<link href="https://cdn.hello.coop/css/hello-btn.css" rel="stylesheet"/>
<script src="https://cdn.hello.coop/js/hello-btn.js"></script>

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<title>fastenhealth</title>
<!-- Fonts and icons -->
<link href="https://fonts.googleapis.com/css?family=Poppins:200,300,400,600,700,800" rel="stylesheet"/>
<link href="https://use.fontawesome.com/releases/v5.0.6/css/all.css" rel="stylesheet"/>
<link href="https://use.fontawesome.com/releases/v5.15.4/css/all.css" rel="stylesheet"/>
<script src="./assets/js/asmcrypto-2.3.2.all.es5.min.js"></script>
<script>
var baseHref = "/"
Expand Down

0 comments on commit cae3afc

Please sign in to comment.