mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
[CST-4875] Created feedback route, created feedback form, created service & guard, utilzed when user is logged in, unit testing and lint check
This commit is contained in:
@@ -71,7 +71,7 @@ export function getMetaReducers(): MetaReducer<AppState>[] {
|
||||
*/
|
||||
export const ValidateEmailErrorStateMatcher: DynamicErrorMessagesMatcher =
|
||||
(control: AbstractControl, model: any, hasFocus: boolean) => {
|
||||
return (control.touched && !hasFocus) || (control.errors?.emailTaken && hasFocus);
|
||||
return (control.touched && !hasFocus) || (control.errors ?.emailTaken && hasFocus);
|
||||
};
|
||||
|
||||
const IMPORTS = [
|
||||
@@ -114,10 +114,10 @@ const PROVIDERS = [
|
||||
// Check the authentication token when the app initializes
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
useFactory: (store: Store<AppState>,) => {
|
||||
useFactory: (store: Store<AppState>, ) => {
|
||||
return () => store.dispatch(new CheckAuthenticationTokenAction());
|
||||
},
|
||||
deps: [ Store ],
|
||||
deps: [Store],
|
||||
multi: true
|
||||
},
|
||||
// register AuthInterceptor as HttpInterceptor
|
||||
@@ -146,7 +146,7 @@ const PROVIDERS = [
|
||||
},
|
||||
// insert the unique id of the user that is using the application utilizing cookies
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
provide: APP_INITIALIZER,
|
||||
useFactory: (cookieService: CookieService, uuidService: UUIDService) => {
|
||||
const correlationId = cookieService.get('CORRELATION-ID');
|
||||
|
||||
@@ -156,8 +156,8 @@ const PROVIDERS = [
|
||||
}
|
||||
return () => true;
|
||||
},
|
||||
multi: true,
|
||||
deps: [ CookieService, UUIDService ]
|
||||
multi: true,
|
||||
deps: [CookieService, UUIDService]
|
||||
},
|
||||
{
|
||||
provide: DYNAMIC_ERROR_MESSAGES_MATCHER,
|
||||
|
@@ -77,6 +77,7 @@ import { MetadataSchema } from './metadata/metadata-schema.model';
|
||||
import { MetadataService } from './metadata/metadata.service';
|
||||
import { RegistryService } from './registry/registry.service';
|
||||
import { RoleService } from './roles/role.service';
|
||||
import { FeedbackDataService } from './feedback/feedback-data.service';
|
||||
|
||||
import { ApiService } from './services/api.service';
|
||||
import { ServerResponseService } from './services/server-response.service';
|
||||
@@ -163,6 +164,7 @@ import { RootDataService } from './data/root-data.service';
|
||||
import { Root } from './data/root.model';
|
||||
import { SearchConfig } from './shared/search/search-filters/search-config.model';
|
||||
import { SequenceService } from './shared/sequence.service';
|
||||
import { FeedbackGuard } from './feedback/feedback.guard';
|
||||
|
||||
/**
|
||||
* When not in production, endpoint responses can be mocked for testing purposes
|
||||
@@ -285,6 +287,8 @@ const PROVIDERS = [
|
||||
VocabularyService,
|
||||
VocabularyTreeviewService,
|
||||
SequenceService,
|
||||
FeedbackDataService,
|
||||
// FeedbackGuard
|
||||
];
|
||||
|
||||
/**
|
||||
|
@@ -25,4 +25,5 @@ export enum FeatureID {
|
||||
CanEditVersion = 'canEditVersion',
|
||||
CanDeleteVersion = 'canDeleteVersion',
|
||||
CanCreateVersion = 'canCreateVersion',
|
||||
CanSendFeedback = 'canSendFeedback',
|
||||
}
|
||||
|
94
src/app/core/feedback/feedback-data.service.ts
Normal file
94
src/app/core/feedback/feedback-data.service.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { AuthService } from '../auth/auth.service';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map, filter, distinctUntilChanged, tap, mergeMap } from 'rxjs/operators';
|
||||
import { isNotEmpty } from '../../shared/empty.util';
|
||||
import { DataService } from '../data/data.service';
|
||||
import { Feedback } from './models/feedback.model';
|
||||
import { FEEDBACK } from './models/feedback.resource-type';
|
||||
import { dataService } from 'src/app/core/cache/builders/build-decorators';
|
||||
import { RequestService } from 'src/app/core/data/request.service';
|
||||
import { RemoteDataBuildService } from 'src/app/core/cache/builders/remote-data-build.service';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { ObjectCacheService } from 'src/app/core/cache/object-cache.service';
|
||||
import { HALEndpointService } from 'src/app/core/shared/hal-endpoint.service';
|
||||
import { NotificationsService } from 'src/app/shared/notifications/notifications.service';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { DSOChangeAnalyzer } from 'src/app/core/data/dso-change-analyzer.service';
|
||||
import { getFirstSucceededRemoteData, getRemoteDataPayload, getFirstCompletedRemoteData } from 'src/app/core/shared/operators';
|
||||
import { HttpOptions } from '../dspace-rest/dspace-rest.service';
|
||||
import { PostRequest } from 'src/app/core/data/request.models';
|
||||
|
||||
/**
|
||||
* Service for checking and managing the status of the current end user agreement
|
||||
*/
|
||||
@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>,
|
||||
protected authService: AuthService, ) {
|
||||
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(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create feedback
|
||||
* @param uuid string the id of the feedback
|
||||
* @return Observable<Feedback>
|
||||
* server response
|
||||
*/
|
||||
createFeedback(payoload: Feedback): Observable<Feedback> {
|
||||
return this.postToEndpoint(this.linkPath, payoload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a new post request
|
||||
*
|
||||
* @param linkName
|
||||
* The endpoint link name
|
||||
* @param body
|
||||
* The post request body
|
||||
* @param options
|
||||
* The [HttpOptions] object
|
||||
* @return Observable<Feedback>
|
||||
* server response
|
||||
*/
|
||||
public postToEndpoint(linkName: string, body: any, options?: HttpOptions): Observable<Feedback> {
|
||||
const requestId = this.requestService.generateRequestId();
|
||||
const href$ = this.halService.getEndpoint(linkName).pipe(
|
||||
filter((href: string) => isNotEmpty(href)),
|
||||
distinctUntilChanged(),
|
||||
map((endpointURL: string) => {
|
||||
const request = new PostRequest(requestId, endpointURL, body, options);
|
||||
return this.requestService.send(request);
|
||||
}),
|
||||
).subscribe();
|
||||
|
||||
return this.rdbService.buildFromRequestUUID<Feedback>(requestId).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
27
src/app/core/feedback/feedback.guard.ts
Normal file
27
src/app/core/feedback/feedback.guard.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
import { AuthorizationDataService } from '../data/feature-authorization/authorization-data.service';
|
||||
import { FeatureID } from 'src/app/core/data/feature-authorization/feature-id';
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
/**
|
||||
* An abstract guard for redirecting users to the user agreement page if a certain condition is met
|
||||
* That condition is defined by abstract method hasAccepted
|
||||
*/
|
||||
@Injectable()
|
||||
export abstract class FeedbackGuard implements CanActivate {
|
||||
|
||||
constructor(protected router: Router, private authorizationService: AuthorizationDataService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* True when the user agreement has been accepted
|
||||
* The user will be redirected to the End User Agreement page if they haven't accepted it before
|
||||
* A redirect URL will be provided with the navigation so the component can redirect the user back to the blocked route
|
||||
* when they're finished accepting the agreement
|
||||
*/
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> {
|
||||
return this.authorizationService.isAuthorized(FeatureID.CanSendFeedback);
|
||||
}
|
||||
|
||||
}
|
37
src/app/core/feedback/models/feedback.model.ts
Normal file
37
src/app/core/feedback/models/feedback.model.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { autoserialize, inheritSerialization } from 'cerialize';
|
||||
import { Observable } from 'rxjs';
|
||||
import { link, typedObject } from '../../cache/builders/build-decorators';
|
||||
import { PaginatedList } from '../../data/paginated-list.model';
|
||||
import { RemoteData } from '../../data/remote-data';
|
||||
|
||||
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 ring 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;
|
||||
};
|
||||
|
||||
}
|
10
src/app/core/feedback/models/feedback.resource-type.ts
Normal file
10
src/app/core/feedback/models/feedback.resource-type.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
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');
|
@@ -40,9 +40,8 @@
|
||||
<h5 class="text-uppercase">Footer Content</h5>
|
||||
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Iste atque ea quis
|
||||
molestias. Fugiat pariatur maxime quis culpa corporis vitae repudiandae aliquam
|
||||
voluptatem veniam, est atque cumque eum delectus sint!
|
||||
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Iste atque ea quis molestias. Fugiat pariatur maxime quis culpa
|
||||
corporis vitae repudiandae aliquam voluptatem veniam, est atque cumque eum delectus sint!
|
||||
</p>
|
||||
</div>
|
||||
<!--Grid column-->
|
||||
@@ -56,27 +55,25 @@
|
||||
<div class="bottom-footer p-1 d-flex justify-content-center align-items-center text-white">
|
||||
<div class="content-container">
|
||||
<p class="m-0">
|
||||
<a class="text-white"
|
||||
href="http://www.dspace.org/">{{ 'footer.link.dspace' | translate}}</a>
|
||||
<a class="text-white" href="http://www.dspace.org/">{{ 'footer.link.dspace' | translate}}</a>
|
||||
{{ 'footer.copyright' | translate:{year: dateObj | date:'y'} }}
|
||||
<a class="text-white"
|
||||
href="https://www.lyrasis.org/">{{ 'footer.link.lyrasis' | translate}}</a>
|
||||
<a class="text-white" href="https://www.lyrasis.org/">{{ 'footer.link.lyrasis' | translate}}</a>
|
||||
</p>
|
||||
<ul class="footer-info list-unstyled small d-flex justify-content-center mb-0">
|
||||
<li>
|
||||
<a class="text-white" href="#"
|
||||
(click)="showCookieSettings()">{{ 'footer.link.cookies' | translate}}</a>
|
||||
<a class="text-white" href="#" (click)="showCookieSettings()">{{ 'footer.link.cookies' | translate}}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="text-white"
|
||||
routerLink="info/privacy">{{ 'footer.link.privacy-policy' | translate}}</a>
|
||||
<a class="text-white" routerLink="info/privacy">{{ 'footer.link.privacy-policy' | translate}}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="text-white"
|
||||
routerLink="info/end-user-agreement">{{ 'footer.link.end-user-agreement' | translate}}</a>
|
||||
<a class="text-white" routerLink="info/end-user-agreement">{{ 'footer.link.end-user-agreement' | translate}}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="text-white" routerLink="info/feedback">{{ 'footer.link.feedback' | translate}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Copyright -->
|
||||
</footer>
|
||||
</footer>
|
@@ -0,0 +1,40 @@
|
||||
<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">Your Email: </label>
|
||||
<input id="email" class="form-control" name="email" type="text" value="" formControlName="email" autofocus="autofocus" title="{{ 'info.feedback.email_help' | translate }}">
|
||||
<p class="help-block">{{ 'info.feedback.email_help' | translate }}</p>
|
||||
</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.pattern' | 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>
|
||||
<input class="form-control" formControlName="page" name="page" type="hidden" value="{{routeService.getPreviousUrl() | async}}"
|
||||
/>
|
||||
<div class="row">
|
||||
<div class="control-group col-sm-12">
|
||||
<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,90 @@
|
||||
import { EPersonMock } from './../../../shared/testing/eperson.mock';
|
||||
import { FeedbackDataService } from './../../../core/feedback/feedback-data.service';
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { FeedbackContentComponent } from './feedback-content.component';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { NO_ERRORS_SCHEMA, DebugElement } from '@angular/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { RouteService } from 'src/app/core/services/route.service';
|
||||
import { routeServiceStub } from 'src/app/shared/testing/route-service.stub';
|
||||
import { FormBuilder } from '@angular/forms';
|
||||
import { NotificationsService } from 'src/app/shared/notifications/notifications.service';
|
||||
import { NotificationsServiceStub } from 'src/app/shared/testing/notifications-service.stub';
|
||||
import { AuthService } from 'src/app/core/auth/auth.service';
|
||||
import { AuthServiceStub } from 'src/app/shared/testing/auth-service.stub';
|
||||
import { of } from 'rxjs/internal/observable/of';
|
||||
import { Feedback } from '../../../core/feedback/models/feedback.model';
|
||||
|
||||
|
||||
describe('FeedbackContentComponent', () => {
|
||||
let component: FeedbackContentComponent;
|
||||
let fixture: ComponentFixture<FeedbackContentComponent>;
|
||||
let de: DebugElement;
|
||||
const notificationService = new NotificationsServiceStub();
|
||||
const feedbackDataServiceStub = jasmine.createSpyObj('feedbackDataService', {
|
||||
createFeedback: of(new Feedback())
|
||||
});
|
||||
const authService: AuthServiceStub = Object.assign(new AuthServiceStub(), {
|
||||
getAuthenticatedUserFromStore: () => {
|
||||
return of(EPersonMock);
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [TranslateModule.forRoot()],
|
||||
declarations: [FeedbackContentComponent],
|
||||
providers: [
|
||||
{ provide: RouteService, useValue: routeServiceStub },
|
||||
{ provide: FormBuilder, useValue: new FormBuilder() },
|
||||
{ provide: NotificationsService, useValue: notificationService },
|
||||
{ provide: FeedbackDataService, useValue: feedbackDataServiceStub },
|
||||
{ provide: AuthService, useValue: authService },
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(FeedbackContentComponent);
|
||||
component = fixture.componentInstance;
|
||||
de = fixture.debugElement;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should have page value', () => {
|
||||
expect(de.query(By.css('input[name=page]')).nativeElement.value).toEqual('/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.createFeedback).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
@@ -0,0 +1,52 @@
|
||||
import { FeedbackDataService } from './../../../core/feedback/feedback-data.service';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { RouteService } from 'src/app/core/services/route.service';
|
||||
import { FormBuilder, Validators } from '@angular/forms';
|
||||
import { Feedback } from '../../../core/feedback/models/feedback.model';
|
||||
import { NotificationsService } from 'src/app/shared/notifications/notifications.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { EPersonDataService } from 'src/app/core/eperson/eperson-data.service';
|
||||
import { AuthService } from 'src/app/core/auth/auth.service';
|
||||
import { EPerson } from '../../../core/eperson/models/eperson.model';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-feedback-content',
|
||||
templateUrl: './feedback-content.component.html',
|
||||
styleUrls: ['./feedback-content.component.scss']
|
||||
})
|
||||
/**
|
||||
* Component displaying the contents of the Feedback Statement
|
||||
*/
|
||||
export class FeedbackContentComponent implements OnInit {
|
||||
|
||||
feedbackForm = this.fb.group({
|
||||
email: ['', [Validators.required, Validators.pattern('^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}$')]],
|
||||
message: ['', Validators.required],
|
||||
page: [''],
|
||||
});
|
||||
|
||||
constructor(
|
||||
public routeService: RouteService,
|
||||
private fb: FormBuilder,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected translate: TranslateService,
|
||||
private feedbackDataService: FeedbackDataService,
|
||||
private authService: AuthService) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.authService.getAuthenticatedUserFromStore().subscribe((user: EPerson) => {
|
||||
if (!!user) {
|
||||
this.feedbackForm.patchValue({ email: user.email });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
createFeedback() {
|
||||
this.feedbackDataService.createFeedback(this.feedbackForm.value).subscribe((response: Feedback) => {
|
||||
this.notificationsService.success(this.translate.instant('info.feedback.create.success'));
|
||||
this.feedbackForm.reset();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
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-content></ds-feedback-content>
|
||||
</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 PRIVACY_PATH = 'privacy';
|
||||
export const FEEDBACK_PATH = 'feedback';
|
||||
|
||||
export function getEndUserAgreementPath() {
|
||||
return getSubPath(END_USER_AGREEMENT_PATH);
|
||||
@@ -11,6 +12,10 @@ export function getPrivacyPath() {
|
||||
return getSubPath(PRIVACY_PATH);
|
||||
}
|
||||
|
||||
export function getFeedbackPath() {
|
||||
return getSubPath(FEEDBACK_PATH);
|
||||
}
|
||||
|
||||
function getSubPath(path: string) {
|
||||
return `${getInfoModulePath()}/${path}`;
|
||||
}
|
||||
|
@@ -1,9 +1,12 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
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 { ThemedPrivacyComponent } from './privacy/themed-privacy.component';
|
||||
import { ThemedFeedbackComponent } from './feedback/themed-feedback.component';
|
||||
import { FeedbackGuard } from '../core/feedback/feedback.guard';
|
||||
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -22,6 +25,15 @@ import { ThemedPrivacyComponent } from './privacy/themed-privacy.component';
|
||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||
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 { ThemedEndUserAgreementComponent } from './end-user-agreement/themed-end-user-agreement.component';
|
||||
import { ThemedPrivacyComponent } from './privacy/themed-privacy.component';
|
||||
import { FeedbackComponent } from './feedback/feedback.component';
|
||||
import { FeedbackContentComponent } from './feedback/feedback-content/feedback-content.component';
|
||||
import { ThemedFeedbackComponent } from './feedback/themed-feedback.component';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
|
||||
|
||||
const DECLARATIONS = [
|
||||
EndUserAgreementComponent,
|
||||
@@ -15,14 +20,17 @@ const DECLARATIONS = [
|
||||
EndUserAgreementContentComponent,
|
||||
PrivacyComponent,
|
||||
PrivacyContentComponent,
|
||||
ThemedPrivacyComponent
|
||||
ThemedPrivacyComponent,
|
||||
FeedbackComponent,
|
||||
FeedbackContentComponent,
|
||||
ThemedFeedbackComponent
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
InfoRoutingModule
|
||||
InfoRoutingModule,
|
||||
],
|
||||
declarations: [
|
||||
...DECLARATIONS
|
||||
|
@@ -29,7 +29,10 @@ export const routeServiceStub: any = {
|
||||
return observableOf({});
|
||||
},
|
||||
getHistory: () => {
|
||||
return observableOf(['/home','/collection/123','/home']);
|
||||
return observableOf(['/home', '/collection/123', '/home']);
|
||||
},
|
||||
getPreviousUrl: () => {
|
||||
return observableOf('/home');
|
||||
}
|
||||
/* tslint:enable:no-empty */
|
||||
};
|
||||
|
@@ -1370,6 +1370,8 @@
|
||||
|
||||
"footer.link.end-user-agreement":"End User Agreement",
|
||||
|
||||
"footer.link.feedback":"Send Feedback",
|
||||
|
||||
|
||||
|
||||
"forgot-email.form.header": "Forgot Password",
|
||||
@@ -1568,6 +1570,27 @@
|
||||
|
||||
"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.create.success" : "Feedback Sent Successfully!",
|
||||
|
||||
"info.feedback.error.email.required" : "Email is required",
|
||||
|
||||
"info.feedback.error.email.pattern" : "Valid Email is required",
|
||||
|
||||
"info.feedback.error.message.required" : "Comment is required",
|
||||
|
||||
|
||||
"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 { }
|
@@ -79,7 +79,8 @@ import { HeaderComponent } from './app/header/header.component';
|
||||
import { FooterComponent } from './app/footer/footer.component';
|
||||
import { BreadcrumbsComponent } from './app/breadcrumbs/breadcrumbs.component';
|
||||
import { HeaderNavbarWrapperComponent } from './app/header-nav-wrapper/header-navbar-wrapper.component';
|
||||
import { FileSectionComponent} from './app/item-page/simple/field-components/file-section/file-section.component';
|
||||
import { FileSectionComponent } from './app/item-page/simple/field-components/file-section/file-section.component';
|
||||
import { FeedbackComponent } from './app/info/feedback/feedback.component';
|
||||
|
||||
const DECLARATIONS = [
|
||||
FileSectionComponent,
|
||||
@@ -121,7 +122,8 @@ const DECLARATIONS = [
|
||||
HeaderComponent,
|
||||
NavbarComponent,
|
||||
HeaderNavbarWrapperComponent,
|
||||
BreadcrumbsComponent
|
||||
BreadcrumbsComponent,
|
||||
FeedbackComponent
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
@@ -172,12 +174,12 @@ const DECLARATIONS = [
|
||||
declarations: DECLARATIONS
|
||||
})
|
||||
|
||||
/**
|
||||
* This module serves as an index for all the components in this theme.
|
||||
* It should import all other modules, so the compiler knows where to find any components referenced
|
||||
* from a component in this theme
|
||||
* It is purposefully not exported, it should never be imported anywhere else, its only purpose is
|
||||
* to give lazily loaded components a context in which they can be compiled successfully
|
||||
*/
|
||||
/**
|
||||
* This module serves as an index for all the components in this theme.
|
||||
* It should import all other modules, so the compiler knows where to find any components referenced
|
||||
* from a component in this theme
|
||||
* It is purposefully not exported, it should never be imported anywhere else, its only purpose is
|
||||
* to give lazily loaded components a context in which they can be compiled successfully
|
||||
*/
|
||||
class ThemeModule {
|
||||
}
|
||||
|
Reference in New Issue
Block a user