mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
Merge pull request #1472 from 4Science/CST-4875-Feedback-form
Feedback form
This commit is contained in:
@@ -32,6 +32,12 @@ export function getBitstreamRequestACopyRoute(item, bitstream): { routerLink: st
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const HOME_PAGE_PATH = 'admin';
|
||||||
|
|
||||||
|
export function getHomePageRoute() {
|
||||||
|
return `/${HOME_PAGE_PATH}`;
|
||||||
|
}
|
||||||
|
|
||||||
export const ADMIN_MODULE_PATH = 'admin';
|
export const ADMIN_MODULE_PATH = 'admin';
|
||||||
|
|
||||||
export function getAdminModuleRoute() {
|
export function getAdminModuleRoute() {
|
||||||
|
@@ -77,6 +77,7 @@ import { MetadataSchema } from './metadata/metadata-schema.model';
|
|||||||
import { MetadataService } from './metadata/metadata.service';
|
import { MetadataService } from './metadata/metadata.service';
|
||||||
import { RegistryService } from './registry/registry.service';
|
import { RegistryService } from './registry/registry.service';
|
||||||
import { RoleService } from './roles/role.service';
|
import { RoleService } from './roles/role.service';
|
||||||
|
import { FeedbackDataService } from './feedback/feedback-data.service';
|
||||||
|
|
||||||
import { ApiService } from './services/api.service';
|
import { ApiService } from './services/api.service';
|
||||||
import { ServerResponseService } from './services/server-response.service';
|
import { ServerResponseService } from './services/server-response.service';
|
||||||
@@ -286,7 +287,8 @@ const PROVIDERS = [
|
|||||||
VocabularyService,
|
VocabularyService,
|
||||||
VocabularyTreeviewService,
|
VocabularyTreeviewService,
|
||||||
SequenceService,
|
SequenceService,
|
||||||
GroupDataService
|
GroupDataService,
|
||||||
|
FeedbackDataService,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -27,4 +27,5 @@ export enum FeatureID {
|
|||||||
CanDeleteVersion = 'canDeleteVersion',
|
CanDeleteVersion = 'canDeleteVersion',
|
||||||
CanCreateVersion = 'canCreateVersion',
|
CanCreateVersion = 'canCreateVersion',
|
||||||
CanViewUsageStatistics = 'canViewUsageStatistics',
|
CanViewUsageStatistics = 'canViewUsageStatistics',
|
||||||
|
CanSendFeedback = 'canSendFeedback',
|
||||||
}
|
}
|
||||||
|
88
src/app/core/feedback/feedback-data.service.spec.ts
Normal file
88
src/app/core/feedback/feedback-data.service.spec.ts
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import { FeedbackDataService } from './feedback-data.service';
|
||||||
|
import { HALLink } from '../shared/hal-link.model';
|
||||||
|
import { Item } from '../shared/item.model';
|
||||||
|
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service.stub';
|
||||||
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
|
import { getMockRequestService } from '../../shared/mocks/request.service.mock';
|
||||||
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { CoreState } from '../core.reducers';
|
||||||
|
import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service';
|
||||||
|
import { Feedback } from './models/feedback.model';
|
||||||
|
|
||||||
|
describe('FeedbackDataService', () => {
|
||||||
|
let service: FeedbackDataService;
|
||||||
|
let requestService;
|
||||||
|
let halService;
|
||||||
|
let rdbService;
|
||||||
|
let notificationsService;
|
||||||
|
let http;
|
||||||
|
let comparator;
|
||||||
|
let objectCache;
|
||||||
|
let store;
|
||||||
|
let item;
|
||||||
|
let bundleLink;
|
||||||
|
let bundleHALLink;
|
||||||
|
|
||||||
|
const feedbackPayload = Object.assign(new Feedback(), {
|
||||||
|
email: 'test@email.com',
|
||||||
|
message: 'message',
|
||||||
|
page: '/home'
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function initTestService(): FeedbackDataService {
|
||||||
|
bundleLink = '/items/0fdc0cd7-ff8c-433d-b33c-9b56108abc07/bundles';
|
||||||
|
bundleHALLink = new HALLink();
|
||||||
|
bundleHALLink.href = bundleLink;
|
||||||
|
item = new Item();
|
||||||
|
item._links = {
|
||||||
|
bundles: bundleHALLink
|
||||||
|
};
|
||||||
|
requestService = getMockRequestService();
|
||||||
|
halService = new HALEndpointServiceStub('url') as any;
|
||||||
|
rdbService = {} as RemoteDataBuildService;
|
||||||
|
notificationsService = {} as NotificationsService;
|
||||||
|
http = {} as HttpClient;
|
||||||
|
comparator = new DSOChangeAnalyzer() as any;
|
||||||
|
objectCache = {
|
||||||
|
|
||||||
|
addPatch: () => {
|
||||||
|
/* empty */
|
||||||
|
},
|
||||||
|
getObjectBySelfLink: () => {
|
||||||
|
/* empty */
|
||||||
|
}
|
||||||
|
} as any;
|
||||||
|
store = {} as Store<CoreState>;
|
||||||
|
return new FeedbackDataService(
|
||||||
|
requestService,
|
||||||
|
rdbService,
|
||||||
|
store,
|
||||||
|
objectCache,
|
||||||
|
halService,
|
||||||
|
notificationsService,
|
||||||
|
http,
|
||||||
|
comparator,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
service = initTestService();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('getFeedback', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(service, 'getFeedback');
|
||||||
|
service.getFeedback('3');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call getFeedback with the feedback link', () => {
|
||||||
|
expect(service.getFeedback).toHaveBeenCalledWith('3');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
49
src/app/core/feedback/feedback-data.service.ts
Normal file
49
src/app/core/feedback/feedback-data.service.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { DataService } from '../data/data.service';
|
||||||
|
import { Feedback } from './models/feedback.model';
|
||||||
|
import { FEEDBACK } from './models/feedback.resource-type';
|
||||||
|
import { dataService } from '../cache/builders/build-decorators';
|
||||||
|
import { RequestService } from '../data/request.service';
|
||||||
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service';
|
||||||
|
import { getFirstSucceededRemoteData, getRemoteDataPayload } from '../shared/operators';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service for checking and managing the feedback
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
@dataService(FEEDBACK)
|
||||||
|
export class FeedbackDataService extends DataService<Feedback> {
|
||||||
|
protected linkPath = 'feedbacks';
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected requestService: RequestService,
|
||||||
|
protected rdbService: RemoteDataBuildService,
|
||||||
|
protected store: Store<any>,
|
||||||
|
protected objectCache: ObjectCacheService,
|
||||||
|
protected halService: HALEndpointService,
|
||||||
|
protected notificationsService: NotificationsService,
|
||||||
|
protected http: HttpClient,
|
||||||
|
protected comparator: DSOChangeAnalyzer<Feedback>,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get feedback from its id
|
||||||
|
* @param uuid string the id of the feedback
|
||||||
|
*/
|
||||||
|
getFeedback(uuid: string): Observable<Feedback> {
|
||||||
|
return this.findById(uuid).pipe(
|
||||||
|
getFirstSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
20
src/app/core/feedback/feedback.guard.ts
Normal file
20
src/app/core/feedback/feedback.guard.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot, UrlTree } from '@angular/router';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { AuthorizationDataService } from '../data/feature-authorization/authorization-data.service';
|
||||||
|
import { FeatureID } from '../data/feature-authorization/feature-id';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An guard for redirecting users to the feedback page if user is authorized
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class FeedbackGuard implements CanActivate {
|
||||||
|
|
||||||
|
constructor(private authorizationService: AuthorizationDataService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> {
|
||||||
|
return this.authorizationService.isAuthorized(FeatureID.CanSendFeedback);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
34
src/app/core/feedback/models/feedback.model.ts
Normal file
34
src/app/core/feedback/models/feedback.model.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { autoserialize, inheritSerialization } from 'cerialize';
|
||||||
|
import { typedObject } from '../../cache/builders/build-decorators';
|
||||||
|
|
||||||
|
import { DSpaceObject } from '../../shared/dspace-object.model';
|
||||||
|
import { HALLink } from '../../shared/hal-link.model';
|
||||||
|
import { FEEDBACK } from './feedback.resource-type';
|
||||||
|
|
||||||
|
@typedObject
|
||||||
|
@inheritSerialization(DSpaceObject)
|
||||||
|
export class Feedback extends DSpaceObject {
|
||||||
|
static type = FEEDBACK;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The email address
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
public email: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A string representing message the user inserted
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
public message: string;
|
||||||
|
/**
|
||||||
|
* A string representing the page from which the user came from
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
public page: string;
|
||||||
|
|
||||||
|
_links: {
|
||||||
|
self: HALLink;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
9
src/app/core/feedback/models/feedback.resource-type.ts
Normal file
9
src/app/core/feedback/models/feedback.resource-type.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { ResourceType } from '../../shared/resource-type';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The resource type for Feedback
|
||||||
|
*
|
||||||
|
* Needs to be in a separate file to prevent circular
|
||||||
|
* dependencies in webpack.
|
||||||
|
*/
|
||||||
|
export const FEEDBACK = new ResourceType('feedback');
|
@@ -75,6 +75,10 @@
|
|||||||
<a class="text-white"
|
<a class="text-white"
|
||||||
routerLink="info/end-user-agreement">{{ 'footer.link.end-user-agreement' | translate}}</a>
|
routerLink="info/end-user-agreement">{{ 'footer.link.end-user-agreement' | translate}}</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="text-white"
|
||||||
|
routerLink="info/feedback">{{ 'footer.link.feedback' | translate}}</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -0,0 +1,45 @@
|
|||||||
|
<div class="row row-offcanvas row-offcanvas-right">
|
||||||
|
<div class="col-xs-12 col-sm-12 col-md-9 main-content">
|
||||||
|
<form class="primary" [formGroup]="feedbackForm" (ngSubmit)="createFeedback()">
|
||||||
|
<h2>{{ 'info.feedback.head' | translate }}</h2>
|
||||||
|
<p>{{ 'info.feedback.info' | translate }}</p>
|
||||||
|
<fieldset class="col p-0">
|
||||||
|
<div class="row">
|
||||||
|
<div class="control-group col-sm-12">
|
||||||
|
<label class="control-label" for="email">{{ 'info.feedback.email-label' | translate }} </label>
|
||||||
|
<input id="email" class="form-control" name="email" type="text" value="" formControlName="email" autofocus="autofocus" title="{{ 'info.feedback.email_help' | translate }}">
|
||||||
|
<small class="text-muted">{{ 'info.feedback.email_help' | translate }}</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-container *ngIf="feedbackForm.controls.email.invalid && (feedbackForm.controls.email.dirty || feedbackForm.controls.email.touched)"
|
||||||
|
class="alert">
|
||||||
|
<ds-error *ngIf="feedbackForm.controls.email.errors?.required" message="{{'info.feedback.error.email.required' | translate}}"></ds-error>
|
||||||
|
<ds-error *ngIf="feedbackForm.controls.email.errors?.pattern" message="{{'info.feedback.error.email.required' | translate}}"></ds-error>
|
||||||
|
</ng-container>
|
||||||
|
<div class="row">
|
||||||
|
<div class="control-group col-sm-12">
|
||||||
|
<label class="control-label" for="comments">{{ 'info.feedback.comments' | translate }}: </label>
|
||||||
|
<textarea id="comments" formControlName="message" class="form-control" name="message" cols="20" rows="5"> </textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ng-container *ngIf="feedbackForm.controls.message.invalid && (feedbackForm.controls.message.dirty || feedbackForm.controls.message.touched)"
|
||||||
|
class="alert">
|
||||||
|
<ds-error *ngIf="feedbackForm.controls.message.errors?.required" message="{{'info.feedback.error.message.required' | translate}}"></ds-error>
|
||||||
|
</ng-container>
|
||||||
|
<div class="row">
|
||||||
|
<div class="control-group col-sm-12">
|
||||||
|
<label class="control-label" for="page">{{ 'info.feedback.page-label' | translate }} </label>
|
||||||
|
<input id="page" readonly class="form-control" name="page" type="text" value="" formControlName="page" autofocus="autofocus" title="{{ 'info.feedback.page_help' | translate }}">
|
||||||
|
<small class="text-muted">{{ 'info.feedback.page_help' | translate }}</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row py-2">
|
||||||
|
<div class="control-group col-sm-12 text-right">
|
||||||
|
<button [disabled]="!feedbackForm.valid" class="btn btn-primary" name="submit" type="submit">{{ 'info.feedback.send' | translate }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -0,0 +1,3 @@
|
|||||||
|
ds-error{
|
||||||
|
color:red;
|
||||||
|
}
|
@@ -0,0 +1,97 @@
|
|||||||
|
import { EPersonMock } from '../../../shared/testing/eperson.mock';
|
||||||
|
import { FeedbackDataService } from '../../../core/feedback/feedback-data.service';
|
||||||
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
|
import { FeedbackFormComponent } from './feedback-form.component';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { RouteService } from '../../../core/services/route.service';
|
||||||
|
import { routeServiceStub } from '../../../shared/testing/route-service.stub';
|
||||||
|
import { FormBuilder } from '@angular/forms';
|
||||||
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
|
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
|
||||||
|
import { AuthService } from '../../../core/auth/auth.service';
|
||||||
|
import { AuthServiceStub } from '../../../shared/testing/auth-service.stub';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { Feedback } from '../../../core/feedback/models/feedback.model';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { RouterMock } from '../../../shared/mocks/router.mock';
|
||||||
|
import { NativeWindowService } from '../../../core/services/window.service';
|
||||||
|
import { NativeWindowMockFactory } from '../../../shared/mocks/mock-native-window-ref';
|
||||||
|
|
||||||
|
|
||||||
|
describe('FeedbackFormComponent', () => {
|
||||||
|
let component: FeedbackFormComponent;
|
||||||
|
let fixture: ComponentFixture<FeedbackFormComponent>;
|
||||||
|
let de: DebugElement;
|
||||||
|
const notificationService = new NotificationsServiceStub();
|
||||||
|
const feedbackDataServiceStub = jasmine.createSpyObj('feedbackDataService', {
|
||||||
|
create: of(new Feedback())
|
||||||
|
});
|
||||||
|
const authService: AuthServiceStub = Object.assign(new AuthServiceStub(), {
|
||||||
|
getAuthenticatedUserFromStore: () => {
|
||||||
|
return of(EPersonMock);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const routerStub = new RouterMock();
|
||||||
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot()],
|
||||||
|
declarations: [FeedbackFormComponent],
|
||||||
|
providers: [
|
||||||
|
{ provide: RouteService, useValue: routeServiceStub },
|
||||||
|
{ provide: FormBuilder, useValue: new FormBuilder() },
|
||||||
|
{ provide: NotificationsService, useValue: notificationService },
|
||||||
|
{ provide: FeedbackDataService, useValue: feedbackDataServiceStub },
|
||||||
|
{ provide: AuthService, useValue: authService },
|
||||||
|
{ provide: NativeWindowService, useFactory: NativeWindowMockFactory },
|
||||||
|
{ provide: Router, useValue: routerStub },
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(FeedbackFormComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
de = fixture.debugElement;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have page value', () => {
|
||||||
|
expect(component.feedbackForm.controls.page.value).toEqual('http://localhost/home');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have email if ePerson', () => {
|
||||||
|
expect(component.feedbackForm.controls.email.value).toEqual('test@test.com');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have disabled button', () => {
|
||||||
|
expect(de.query(By.css('button')).nativeElement.disabled).toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when message is inserted', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
component.feedbackForm.patchValue({ message: 'new feedback' });
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not have disabled button', () => {
|
||||||
|
expect(de.query(By.css('button')).nativeElement.disabled).toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('on submit should call createFeedback of feedbackDataServiceStub service', () => {
|
||||||
|
component.createFeedback();
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(feedbackDataServiceStub.create).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
@@ -0,0 +1,83 @@
|
|||||||
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
|
import { NoContent } from '../../../core/shared/NoContent.model';
|
||||||
|
import { FeedbackDataService } from '../../../core/feedback/feedback-data.service';
|
||||||
|
import { Component, Inject, OnInit } from '@angular/core';
|
||||||
|
import { RouteService } from '../../../core/services/route.service';
|
||||||
|
import { FormBuilder, Validators } from '@angular/forms';
|
||||||
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { AuthService } from '../../../core/auth/auth.service';
|
||||||
|
import { EPerson } from '../../../core/eperson/models/eperson.model';
|
||||||
|
import { getFirstCompletedRemoteData } from '../../../core/shared/operators';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { getHomePageRoute } from '../../../app-routing-paths';
|
||||||
|
import { take } from 'rxjs/operators';
|
||||||
|
import { NativeWindowRef, NativeWindowService } from '../../../core/services/window.service';
|
||||||
|
import { URLCombiner } from '../../../core/url-combiner/url-combiner';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-feedback-form',
|
||||||
|
templateUrl: './feedback-form.component.html',
|
||||||
|
styleUrls: ['./feedback-form.component.scss']
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component displaying the contents of the Feedback Statement
|
||||||
|
*/
|
||||||
|
export class FeedbackFormComponent implements OnInit {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Form builder created used from the feedback from
|
||||||
|
*/
|
||||||
|
feedbackForm = this.fb.group({
|
||||||
|
email: ['', [Validators.required, Validators.pattern('^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}$')]],
|
||||||
|
message: ['', Validators.required],
|
||||||
|
page: [''],
|
||||||
|
});
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(NativeWindowService) protected _window: NativeWindowRef,
|
||||||
|
public routeService: RouteService,
|
||||||
|
private fb: FormBuilder,
|
||||||
|
protected notificationsService: NotificationsService,
|
||||||
|
protected translate: TranslateService,
|
||||||
|
private feedbackDataService: FeedbackDataService,
|
||||||
|
private authService: AuthService,
|
||||||
|
private router: Router) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On init check if user is logged in and use its email if so
|
||||||
|
*/
|
||||||
|
ngOnInit() {
|
||||||
|
|
||||||
|
this.authService.getAuthenticatedUserFromStore().pipe(take(1)).subscribe((user: EPerson) => {
|
||||||
|
if (!!user) {
|
||||||
|
this.feedbackForm.patchValue({ email: user.email });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.routeService.getPreviousUrl().pipe(take(1)).subscribe((url: string) => {
|
||||||
|
if (!url) {
|
||||||
|
url = getHomePageRoute();
|
||||||
|
}
|
||||||
|
const relatedUrl = new URLCombiner(this._window.nativeWindow.origin, url).toString();
|
||||||
|
this.feedbackForm.patchValue({ page: relatedUrl });
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to create the feedback from form values
|
||||||
|
*/
|
||||||
|
createFeedback(): void {
|
||||||
|
const url = this.feedbackForm.value.page.replace(this._window.nativeWindow.origin, '');
|
||||||
|
this.feedbackDataService.create(this.feedbackForm.value).pipe(getFirstCompletedRemoteData()).subscribe((response: RemoteData<NoContent>) => {
|
||||||
|
if (response.isSuccess) {
|
||||||
|
this.notificationsService.success(this.translate.instant('info.feedback.create.success'));
|
||||||
|
this.feedbackForm.reset();
|
||||||
|
this.router.navigateByUrl(url);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
3
src/app/info/feedback/feedback.component.html
Normal file
3
src/app/info/feedback/feedback.component.html
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<div class="container">
|
||||||
|
<ds-feedback-form></ds-feedback-form>
|
||||||
|
</div>
|
0
src/app/info/feedback/feedback.component.scss
Normal file
0
src/app/info/feedback/feedback.component.scss
Normal file
27
src/app/info/feedback/feedback.component.spec.ts
Normal file
27
src/app/info/feedback/feedback.component.spec.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
|
import { FeedbackComponent } from './feedback.component';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
|
||||||
|
describe('FeedbackComponent', () => {
|
||||||
|
let component: FeedbackComponent;
|
||||||
|
let fixture: ComponentFixture<FeedbackComponent>;
|
||||||
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot()],
|
||||||
|
declarations: [FeedbackComponent],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(FeedbackComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
12
src/app/info/feedback/feedback.component.ts
Normal file
12
src/app/info/feedback/feedback.component.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-feedback',
|
||||||
|
templateUrl: './feedback.component.html',
|
||||||
|
styleUrls: ['./feedback.component.scss']
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component displaying the Feedback Statement
|
||||||
|
*/
|
||||||
|
export class FeedbackComponent {
|
||||||
|
}
|
26
src/app/info/feedback/themed-feedback.component.ts
Normal file
26
src/app/info/feedback/themed-feedback.component.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { ThemedComponent } from '../../shared/theme-support/themed.component';
|
||||||
|
import { FeedbackComponent } from './feedback.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Themed wrapper for FeedbackComponent
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-themed-feedback',
|
||||||
|
styleUrls: [],
|
||||||
|
templateUrl: '../../shared/theme-support/themed.component.html',
|
||||||
|
})
|
||||||
|
export class ThemedFeedbackComponent extends ThemedComponent<FeedbackComponent> {
|
||||||
|
protected getComponentName(): string {
|
||||||
|
return 'FeedbackComponent';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importThemedComponent(themeName: string): Promise<any> {
|
||||||
|
return import(`../../../themes/${themeName}/app/info/feedback/feedback.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importUnthemedComponent(): Promise<any> {
|
||||||
|
return import(`./feedback.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -2,6 +2,7 @@ import { getInfoModulePath } from '../app-routing-paths';
|
|||||||
|
|
||||||
export const END_USER_AGREEMENT_PATH = 'end-user-agreement';
|
export const END_USER_AGREEMENT_PATH = 'end-user-agreement';
|
||||||
export const PRIVACY_PATH = 'privacy';
|
export const PRIVACY_PATH = 'privacy';
|
||||||
|
export const FEEDBACK_PATH = 'feedback';
|
||||||
|
|
||||||
export function getEndUserAgreementPath() {
|
export function getEndUserAgreementPath() {
|
||||||
return getSubPath(END_USER_AGREEMENT_PATH);
|
return getSubPath(END_USER_AGREEMENT_PATH);
|
||||||
@@ -11,6 +12,10 @@ export function getPrivacyPath() {
|
|||||||
return getSubPath(PRIVACY_PATH);
|
return getSubPath(PRIVACY_PATH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getFeedbackPath() {
|
||||||
|
return getSubPath(FEEDBACK_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
function getSubPath(path: string) {
|
function getSubPath(path: string) {
|
||||||
return `${getInfoModulePath()}/${path}`;
|
return `${getInfoModulePath()}/${path}`;
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,12 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
|
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||||
import { PRIVACY_PATH, END_USER_AGREEMENT_PATH } from './info-routing-paths';
|
import { PRIVACY_PATH, END_USER_AGREEMENT_PATH, FEEDBACK_PATH } from './info-routing-paths';
|
||||||
import { ThemedEndUserAgreementComponent } from './end-user-agreement/themed-end-user-agreement.component';
|
import { ThemedEndUserAgreementComponent } from './end-user-agreement/themed-end-user-agreement.component';
|
||||||
import { ThemedPrivacyComponent } from './privacy/themed-privacy.component';
|
import { ThemedPrivacyComponent } from './privacy/themed-privacy.component';
|
||||||
|
import { ThemedFeedbackComponent } from './feedback/themed-feedback.component';
|
||||||
|
import { FeedbackGuard } from '../core/feedback/feedback.guard';
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -22,6 +25,15 @@ import { ThemedPrivacyComponent } from './privacy/themed-privacy.component';
|
|||||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||||
data: { title: 'info.privacy.title', breadcrumbKey: 'info.privacy' }
|
data: { title: 'info.privacy.title', breadcrumbKey: 'info.privacy' }
|
||||||
}
|
}
|
||||||
|
]),
|
||||||
|
RouterModule.forChild([
|
||||||
|
{
|
||||||
|
path: FEEDBACK_PATH,
|
||||||
|
component: ThemedFeedbackComponent,
|
||||||
|
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||||
|
data: { title: 'info.feedback.title', breadcrumbKey: 'info.feedback' },
|
||||||
|
canActivate: [FeedbackGuard]
|
||||||
|
}
|
||||||
])
|
])
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
@@ -8,6 +8,11 @@ import { PrivacyComponent } from './privacy/privacy.component';
|
|||||||
import { PrivacyContentComponent } from './privacy/privacy-content/privacy-content.component';
|
import { PrivacyContentComponent } from './privacy/privacy-content/privacy-content.component';
|
||||||
import { ThemedEndUserAgreementComponent } from './end-user-agreement/themed-end-user-agreement.component';
|
import { ThemedEndUserAgreementComponent } from './end-user-agreement/themed-end-user-agreement.component';
|
||||||
import { ThemedPrivacyComponent } from './privacy/themed-privacy.component';
|
import { ThemedPrivacyComponent } from './privacy/themed-privacy.component';
|
||||||
|
import { FeedbackComponent } from './feedback/feedback.component';
|
||||||
|
import { FeedbackFormComponent } from './feedback/feedback-form/feedback-form.component';
|
||||||
|
import { ThemedFeedbackComponent } from './feedback/themed-feedback.component';
|
||||||
|
import { FeedbackGuard } from '../core/feedback/feedback.guard';
|
||||||
|
|
||||||
|
|
||||||
const DECLARATIONS = [
|
const DECLARATIONS = [
|
||||||
EndUserAgreementComponent,
|
EndUserAgreementComponent,
|
||||||
@@ -15,21 +20,25 @@ const DECLARATIONS = [
|
|||||||
EndUserAgreementContentComponent,
|
EndUserAgreementContentComponent,
|
||||||
PrivacyComponent,
|
PrivacyComponent,
|
||||||
PrivacyContentComponent,
|
PrivacyContentComponent,
|
||||||
ThemedPrivacyComponent
|
ThemedPrivacyComponent,
|
||||||
|
FeedbackComponent,
|
||||||
|
FeedbackFormComponent,
|
||||||
|
ThemedFeedbackComponent
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
InfoRoutingModule
|
InfoRoutingModule,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
...DECLARATIONS
|
...DECLARATIONS
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
...DECLARATIONS
|
...DECLARATIONS
|
||||||
]
|
],
|
||||||
|
providers: [FeedbackGuard]
|
||||||
})
|
})
|
||||||
export class InfoModule {
|
export class InfoModule {
|
||||||
}
|
}
|
||||||
|
@@ -7,7 +7,8 @@ export const MockWindow = {
|
|||||||
get href() {
|
get href() {
|
||||||
return this._href;
|
return this._href;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
origin: 'http://localhost'
|
||||||
};
|
};
|
||||||
|
|
||||||
export class NativeWindowRefMock {
|
export class NativeWindowRefMock {
|
||||||
|
@@ -29,7 +29,10 @@ export const routeServiceStub: any = {
|
|||||||
return observableOf({});
|
return observableOf({});
|
||||||
},
|
},
|
||||||
getHistory: () => {
|
getHistory: () => {
|
||||||
return observableOf(['/home','/collection/123','/home']);
|
return observableOf(['/home', '/collection/123', '/home']);
|
||||||
|
},
|
||||||
|
getPreviousUrl: () => {
|
||||||
|
return observableOf('/home');
|
||||||
}
|
}
|
||||||
/* tslint:enable:no-empty */
|
/* tslint:enable:no-empty */
|
||||||
};
|
};
|
||||||
|
@@ -1377,6 +1377,8 @@
|
|||||||
|
|
||||||
"footer.link.end-user-agreement":"End User Agreement",
|
"footer.link.end-user-agreement":"End User Agreement",
|
||||||
|
|
||||||
|
"footer.link.feedback":"Send Feedback",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"forgot-email.form.header": "Forgot Password",
|
"forgot-email.form.header": "Forgot Password",
|
||||||
@@ -1575,6 +1577,32 @@
|
|||||||
|
|
||||||
"info.privacy.title": "Privacy Statement",
|
"info.privacy.title": "Privacy Statement",
|
||||||
|
|
||||||
|
"info.feedback.breadcrumbs": "Feedback",
|
||||||
|
|
||||||
|
"info.feedback.head": "Feedback",
|
||||||
|
|
||||||
|
"info.feedback.title": "Feedback",
|
||||||
|
|
||||||
|
"info.feedback.info": "Thanks for sharing your feedback about the DSpace system. Your comments are appreciated!",
|
||||||
|
|
||||||
|
"info.feedback.email_help": "This address will be used to follow up on your feedback.",
|
||||||
|
|
||||||
|
"info.feedback.send": "Send Feedback",
|
||||||
|
|
||||||
|
"info.feedback.comments": "Comments",
|
||||||
|
|
||||||
|
"info.feedback.email-label": "Your Email",
|
||||||
|
|
||||||
|
"info.feedback.create.success" : "Feedback Sent Successfully!",
|
||||||
|
|
||||||
|
"info.feedback.error.email.required" : "A valid email address is required",
|
||||||
|
|
||||||
|
"info.feedback.error.message.required" : "A comment is required",
|
||||||
|
|
||||||
|
"info.feedback.page-label" : "Page",
|
||||||
|
|
||||||
|
"info.feedback.page_help" : "Tha page related to your feedback",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"item.alerts.private": "This item is private",
|
"item.alerts.private": "This item is private",
|
||||||
|
15
src/themes/custom/app/info/feedback/feedback.component.ts
Normal file
15
src/themes/custom/app/info/feedback/feedback.component.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { FeedbackComponent as BaseComponent } from '../../../../../app/info/feedback/feedback.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-feedback',
|
||||||
|
// styleUrls: ['./feedback.component.scss'],
|
||||||
|
styleUrls: ['../../../../../app/info/feedback/feedback.component.scss'],
|
||||||
|
// templateUrl: './feedback.component.html'
|
||||||
|
templateUrl: '../../../../../app/info/feedback/feedback.component.html'
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component displaying the feedback Statement
|
||||||
|
*/
|
||||||
|
export class FeedbackComponent extends BaseComponent { }
|
@@ -83,6 +83,7 @@ import { FileSectionComponent } from './app/item-page/simple/field-components/fi
|
|||||||
import { SearchModule } from '../../app/shared/search/search.module';
|
import { SearchModule } from '../../app/shared/search/search.module';
|
||||||
import { ResourcePoliciesModule } from '../../app/shared/resource-policies/resource-policies.module';
|
import { ResourcePoliciesModule } from '../../app/shared/resource-policies/resource-policies.module';
|
||||||
import { ComcolModule } from '../../app/shared/comcol/comcol.module';
|
import { ComcolModule } from '../../app/shared/comcol/comcol.module';
|
||||||
|
import { FeedbackComponent } from './app/info/feedback/feedback.component';
|
||||||
|
|
||||||
const DECLARATIONS = [
|
const DECLARATIONS = [
|
||||||
FileSectionComponent,
|
FileSectionComponent,
|
||||||
@@ -124,7 +125,8 @@ const DECLARATIONS = [
|
|||||||
HeaderComponent,
|
HeaderComponent,
|
||||||
NavbarComponent,
|
NavbarComponent,
|
||||||
HeaderNavbarWrapperComponent,
|
HeaderNavbarWrapperComponent,
|
||||||
BreadcrumbsComponent
|
BreadcrumbsComponent,
|
||||||
|
FeedbackComponent
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
Reference in New Issue
Block a user