diff --git a/frontend/angular.json b/frontend/angular.json index cdb87d9292..9c586609e2 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -36,8 +36,8 @@ "budgets": [ { "type": "initial", - "maximumWarning": "500kB", - "maximumError": "1MB" + "maximumWarning": "2MB", + "maximumError": "3MB" }, { "type": "anyComponentStyle", diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 660a734834..73223a55f9 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -17,6 +17,7 @@ "@angular/platform-browser-dynamic": "^18.2.0", "@angular/router": "^18.2.0", "primeflex": "^3.3.1", + "primeicons": "^7.0.0", "primeng": "^17.18.10", "rxjs": "~7.8.0", "tslib": "^2.3.0", @@ -13595,6 +13596,12 @@ "integrity": "sha512-zaOq3YvcOYytbAmKv3zYc+0VNS9Wg5d37dfxZnveKBFPr7vEIwfV5ydrpiouTft8MVW6qNjfkaQphHSnvgQbpQ==", "license": "MIT" }, + "node_modules/primeicons": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/primeicons/-/primeicons-7.0.0.tgz", + "integrity": "sha512-jK3Et9UzwzTsd6tzl2RmwrVY/b8raJ3QZLzoDACj+oTJ0oX7L9Hy+XnVwgo4QVKlKpnP/Ur13SXV/pVh4LzaDw==", + "license": "MIT" + }, "node_modules/primeng": { "version": "17.18.10", "resolved": "https://registry.npmjs.org/primeng/-/primeng-17.18.10.tgz", diff --git a/frontend/package.json b/frontend/package.json index be51c8b809..cbcceb197f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -21,6 +21,7 @@ "@angular/platform-browser-dynamic": "^18.2.0", "@angular/router": "^18.2.0", "primeflex": "^3.3.1", + "primeicons": "^7.0.0", "primeng": "^17.18.10", "rxjs": "~7.8.0", "tslib": "^2.3.0", diff --git a/frontend/src/_services/question.service.spec.ts b/frontend/src/_services/question.service.spec.ts new file mode 100644 index 0000000000..1db8a54876 --- /dev/null +++ b/frontend/src/_services/question.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { QuestionService } from './question.service'; + +describe('QuestionService', () => { + let service: QuestionService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(QuestionService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/frontend/src/_services/question.service.ts b/frontend/src/_services/question.service.ts new file mode 100644 index 0000000000..5760b77987 --- /dev/null +++ b/frontend/src/_services/question.service.ts @@ -0,0 +1,87 @@ +import { HttpClient, HttpErrorResponse, HttpHeaders, HttpParams } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { API_CONFIG } from '../app/api.config'; +import { catchError, Observable, throwError } from 'rxjs'; +import { SingleQuestionResponse, QuestionResponse, QuestionBody } from '../app/questions/question.model'; +import { TopicResponse } from '../app/questions/topic.model'; + +@Injectable({ + providedIn: 'root', +}) +export class QuestionService { + private baseUrl = API_CONFIG.baseUrl; + + private httpOptions = { + headers: new HttpHeaders({ + 'Content-Type': 'application/json', + }), + }; + + constructor(private http: HttpClient) {} + + getQuestions( + title?: string, + description?: string, + topics?: string[], + difficulty?: string, + ): Observable { + let params = new HttpParams(); + + if (title) { + params = params.append('title', title); + } + if (description) { + params = params.append('description', description); + } + if (topics && topics.length > 0) { + params = params.append('topics', topics.join(',')); + } + if (difficulty) { + params = params.append('difficulty', difficulty); + } + + // send request + return this.http.get(this.baseUrl + '/questions', { params }); + } + + getQuestionByID(id: number): Observable { + return this.http.get(this.baseUrl + '/questions/' + id); + } + + getQuestionByParam(topics: string[], difficulty: string, limit?: number): Observable { + let params = new HttpParams(); + + if (limit) { + params = params.append('limit', limit); + } + params = params.append('topics', topics.join(',')).append('difficulty', difficulty); + + return this.http.get(this.baseUrl + '/questions/search', { params }); + } + + getTopics(): Observable { + return this.http.get(this.baseUrl + '/questions/topics'); + } + + addQuestion(question: QuestionBody): Observable { + return this.http + .post(this.baseUrl + '/questions', question, this.httpOptions) + .pipe(catchError(this.handleError)); + } + + updateQuestion(id: number, question: QuestionBody): Observable { + return this.http + .put(this.baseUrl + '/questions/' + id, question, this.httpOptions) + .pipe(catchError(this.handleError)); + } + + deleteQuestion(id: number): Observable { + return this.http + .delete(this.baseUrl + '/questions/' + id) + .pipe(catchError(this.handleError)); + } + + handleError(error: HttpErrorResponse) { + return throwError(error); + } +} diff --git a/frontend/src/app/api.config.ts b/frontend/src/app/api.config.ts new file mode 100644 index 0000000000..67e2d1a966 --- /dev/null +++ b/frontend/src/app/api.config.ts @@ -0,0 +1,3 @@ +export const API_CONFIG = { + baseUrl: 'http://localhost:8081', +}; diff --git a/frontend/src/app/app.config.ts b/frontend/src/app/app.config.ts index 6be3e3d257..b2971cbc23 100644 --- a/frontend/src/app/app.config.ts +++ b/frontend/src/app/app.config.ts @@ -1,9 +1,14 @@ import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; import { provideRouter } from '@angular/router'; - import { routes } from './app.routes'; import { provideAnimationsAsync } from '@angular/platform-browser/animations/async'; +import { provideHttpClient } from '@angular/common/http'; export const appConfig: ApplicationConfig = { - providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideAnimationsAsync()], + providers: [ + provideZoneChangeDetection({ eventCoalescing: true }), + provideRouter(routes), + provideAnimationsAsync(), + provideHttpClient(), + ], }; diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts index 971b436069..85a8e48852 100644 --- a/frontend/src/app/app.routes.ts +++ b/frontend/src/app/app.routes.ts @@ -1,4 +1,5 @@ import { Routes } from '@angular/router'; +import { QuestionsComponent } from './questions/questions.component'; const accountModule = () => import('./account/account.module').then(x => x.AccountModule); @@ -7,4 +8,8 @@ export const routes: Routes = [ path: 'account', loadChildren: accountModule, }, + { + path: 'questions', + component: QuestionsComponent, + }, ]; diff --git a/frontend/src/app/questions/column.model.ts b/frontend/src/app/questions/column.model.ts new file mode 100644 index 0000000000..2462f0c027 --- /dev/null +++ b/frontend/src/app/questions/column.model.ts @@ -0,0 +1,4 @@ +export interface Column { + field: string; + header: string; +} diff --git a/frontend/src/app/questions/difficulty-levels.enum.ts b/frontend/src/app/questions/difficulty-levels.enum.ts new file mode 100644 index 0000000000..6ef37f3254 --- /dev/null +++ b/frontend/src/app/questions/difficulty-levels.enum.ts @@ -0,0 +1,5 @@ +export enum DifficultyLevels { + EASY = 'Easy', + MEDIUM = 'Medium', + HARD = 'Hard', +} diff --git a/frontend/src/app/questions/difficulty.model.ts b/frontend/src/app/questions/difficulty.model.ts new file mode 100644 index 0000000000..0479b381f1 --- /dev/null +++ b/frontend/src/app/questions/difficulty.model.ts @@ -0,0 +1,4 @@ +export interface Difficulty { + label: string; + value: string; +} diff --git a/frontend/src/app/questions/question-dialog.component.css b/frontend/src/app/questions/question-dialog.component.css new file mode 100644 index 0000000000..516b236f7b --- /dev/null +++ b/frontend/src/app/questions/question-dialog.component.css @@ -0,0 +1,22 @@ +.container { + padding: 2rem; + background-color: var(--surface-section); + border-radius: 0.75rem; + display: flex; + flex-direction: column; + align-items: center; +} + +.form-container { + display: flex; + flex-direction: column; + gap: 1rem; + width: 100%; +} + +.form-field { + display: flex; + flex-direction: column; + gap: 0.5rem; + width: 100%; +} \ No newline at end of file diff --git a/frontend/src/app/questions/question-dialog.component.html b/frontend/src/app/questions/question-dialog.component.html new file mode 100644 index 0000000000..d6c25dd88e --- /dev/null +++ b/frontend/src/app/questions/question-dialog.component.html @@ -0,0 +1,102 @@ + + +
+ {{ headerMessage }} +
+
+ +
+
+ + + @if (isTitleInvalid) { + Title is required. + } +
+
+ + + + + {{ option.label }} + + + + @if (hasNoResultsFound) { + + } + + + @if (isTopicsInvalid) { + Topic(s) is required. + } +
+
+ + + + @if (isDifficultyInvalid) { + Difficulty is required. + } +
+
+ + + @if (isDescriptionInvalid) { + Description is required. + } +
+
+ + + + + +
diff --git a/frontend/src/app/questions/question-dialog.component.spec.ts b/frontend/src/app/questions/question-dialog.component.spec.ts new file mode 100644 index 0000000000..2fa7d913a0 --- /dev/null +++ b/frontend/src/app/questions/question-dialog.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { QuestionDialogComponent } from './question-dialog.component'; + +describe('QuestionDialogComponent', () => { + let component: QuestionDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [QuestionDialogComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(QuestionDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/questions/question-dialog.component.ts b/frontend/src/app/questions/question-dialog.component.ts new file mode 100644 index 0000000000..3d11e7ee1e --- /dev/null +++ b/frontend/src/app/questions/question-dialog.component.ts @@ -0,0 +1,235 @@ +import { Component, EventEmitter, Input, Output, OnInit, ViewChild } from '@angular/core'; +import { Question, QuestionBody, SingleQuestionResponse } from './question.model'; +import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { DialogModule } from 'primeng/dialog'; +import { MultiSelect, MultiSelectModule } from 'primeng/multiselect'; +import { DropdownModule } from 'primeng/dropdown'; +import { ButtonModule } from 'primeng/button'; +import { ConfirmationService, MessageService } from 'primeng/api'; +import { QuestionService } from '../../_services/question.service'; +import { Difficulty } from './difficulty.model'; +import { Topic } from './topic.model'; +import { HttpErrorResponse } from '@angular/common/http'; +import { DifficultyLevels } from './difficulty-levels.enum'; + +@Component({ + selector: 'app-question-dialog', + standalone: true, + imports: [ + ButtonModule, + DialogModule, + ButtonModule, + ReactiveFormsModule, + MultiSelectModule, + DropdownModule, + QuestionDialogComponent, + ], + providers: [QuestionService, ConfirmationService, MessageService], + templateUrl: './question-dialog.component.html', + styleUrl: './question-dialog.component.css', +}) +export class QuestionDialogComponent implements OnInit { + @ViewChild('topicSelector') topicSelector!: MultiSelect; + @Input() question!: Question; + @Input() isDialogVisible = false; + @Output() dialogClose = new EventEmitter(); + @Output() questionUpdate = new EventEmitter(); + @Output() questionAdd = new EventEmitter(); + @Output() errorReceive = new EventEmitter(); + @Output() successfulRequest = new EventEmitter(); + + questionFormGroup!: FormGroup; + submitted = false; + + topicSearchValue = ''; + + headerMessage = ''; + + topics!: Topic[]; + + difficulties!: Difficulty[]; + + filteredTopics: Topic[] = []; + + hasNoResultsFound = false; + + constructor(private questionService: QuestionService) {} + + ngOnInit(): void { + this.initFormGroup(); + + this.initDifficulties(); + + this.initTopics(); + } + + get topicControl(): FormControl { + return this.questionFormGroup.controls['topics'] as FormControl; + } + + get isTitleInvalid(): boolean { + const titleControl = this.questionFormGroup.controls['title']; + return titleControl.dirty && titleControl.invalid; + } + + get isDescriptionInvalid(): boolean { + const descriptionControl = this.questionFormGroup.controls['description']; + return descriptionControl.dirty && descriptionControl.invalid; + } + + get isDifficultyInvalid(): boolean { + const difficultyControl = this.questionFormGroup.controls['difficulty']; + return difficultyControl.dirty && difficultyControl.invalid; + } + + get isTopicsInvalid(): boolean { + const topicsControl = this.questionFormGroup.controls['topics']; + return topicsControl.dirty && topicsControl.invalid; + } + + saveQuestion() { + this.submitted = true; + + if (!this.questionFormGroup.valid) { + return; + } + + if (this.question.id) { + // update + this.handleEditQuestionResponse(this.question.id, this.questionFormGroup.value); + } else { + // add + this.handleAddQuestionResponse(); + } + + this.dialogClose.emit(); + } + + cancel() { + this.resetFormGroup(); + this.dialogClose.emit(); + } + + show() { + this.setFormValue(); + this.setHeaderMessage(); + } + + setHeaderMessage() { + if (this.question.id) { + this.headerMessage = 'Edit Question'; + } else { + this.submitted = false; + this.headerMessage = 'Create new question'; + } + } + + handleEditQuestionResponse(id: number, question: QuestionBody) { + this.questionService.updateQuestion(id, question).subscribe({ + next: (response: SingleQuestionResponse) => { + this.questionUpdate.emit(response.data); + }, + error: (error: HttpErrorResponse) => { + this.errorReceive.emit(error.error.message); + }, + complete: () => { + this.question = {} as Question; + this.successfulRequest.emit('Question has been updated successfully'); + }, + }); + } + + handleAddQuestionResponse() { + this.questionService.addQuestion(this.questionFormGroup.value).subscribe({ + next: (response: SingleQuestionResponse) => { + this.questionAdd.emit(response.data); + }, + error: (error: HttpErrorResponse) => { + this.errorReceive.emit('Failed to add new question. ' + error.error.message); + }, + complete: () => { + this.question = {} as Question; + this.successfulRequest.emit('New Question Added'); + }, + }); + } + + initFormGroup() { + this.questionFormGroup = new FormGroup({ + topics: new FormControl([], [Validators.required]), + difficulty: new FormControl([], [Validators.required]), + title: new FormControl('', [Validators.required]), + description: new FormControl('', [Validators.required]), + }); + } + + initDifficulties() { + this.difficulties = [ + { label: DifficultyLevels.EASY, value: DifficultyLevels.EASY }, + { label: DifficultyLevels.MEDIUM, value: DifficultyLevels.MEDIUM }, + { label: DifficultyLevels.HARD, value: DifficultyLevels.HARD }, + ]; + } + + initTopics() { + this.questionService.getTopics().subscribe({ + next: response => { + this.topics = + response.data?.map(topic => ({ + label: topic, + value: topic, + })) || []; + }, + error: (error: HttpErrorResponse) => { + this.topics = []; + this.errorReceive.emit('Failed to load topics. ' + error.error.message); + }, + }); + } + + setFormValue() { + this.questionFormGroup.patchValue({ + title: this.question.title, + description: this.question.description, + topics: this.question.topics, + difficulty: this.question.difficulty, + }); + } + + resetFormGroup() { + this.questionFormGroup.reset({ + topics: [], + difficulty: '', + title: '', + description: '', + }); + } + + onFilterTopics(event: { filter: string }) { + this.topicSearchValue = event.filter; + this.hasNoResultsFound = !this.topics.some(topic => + topic.label.toLowerCase().includes(this.topicSearchValue.toLowerCase()), + ); + } + + addNewTopic() { + const newTopic = this.topicSearchValue; + const newValue: Topic = { + label: newTopic, + value: newTopic, + }; + + const topicExists = this.topics.map(t => t.label).some(l => l.toLowerCase() === newTopic.toLowerCase()); + + if (topicExists) { + return; + } + + this.topics.push(newValue); + + // Immediately add the new topic, and clear the search filter + this.topicControl.setValue(this.topicControl.value?.concat([newTopic]) ?? [newTopic]); + this.topicSelector.resetFilter(); + this.hasNoResultsFound = false; + } +} diff --git a/frontend/src/app/questions/question.model.ts b/frontend/src/app/questions/question.model.ts new file mode 100644 index 0000000000..828b5ee2bd --- /dev/null +++ b/frontend/src/app/questions/question.model.ts @@ -0,0 +1,26 @@ +export interface QuestionResponse { + status: string; + message: string; + data?: Question[] | null; +} + +export interface SingleQuestionResponse { + status: string; + message: string; + data: Question; +} + +export interface Question { + id: number; + description: string; + difficulty: string; + title: string; + topics?: string[]; +} + +export interface QuestionBody { + description?: string; + difficulty?: string; + title?: string; + topics?: string[]; +} diff --git a/frontend/src/app/questions/questions.component.css b/frontend/src/app/questions/questions.component.css new file mode 100644 index 0000000000..b6e58b6a47 --- /dev/null +++ b/frontend/src/app/questions/questions.component.css @@ -0,0 +1,7 @@ +.p-sortable-column { + background-color: #181818 !important; +} + +.p-sortable-column:not(.p-highlight):hover { + background-color: #181818 !important; +} \ No newline at end of file diff --git a/frontend/src/app/questions/questions.component.html b/frontend/src/app/questions/questions.component.html new file mode 100644 index 0000000000..da2cd6473a --- /dev/null +++ b/frontend/src/app/questions/questions.component.html @@ -0,0 +1,88 @@ +
+ + + + +
+ + +
+
+ + +
+

Manage Questions

+
+
+ + + + Id + Title + Description + Topics + Difficulty + + + + + + + + + {{ question.id }} + {{ question.title }} + {{ question.description }} + {{ question.topics.join(', ') }} + {{ question.difficulty }} + + + + + +
+
+ + + +
diff --git a/frontend/src/app/questions/questions.component.spec.ts b/frontend/src/app/questions/questions.component.spec.ts new file mode 100644 index 0000000000..4ba0f70cfc --- /dev/null +++ b/frontend/src/app/questions/questions.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { QuestionsComponent } from './questions.component'; + +describe('QuestionsComponent', () => { + let component: QuestionsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [QuestionsComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(QuestionsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/questions/questions.component.ts b/frontend/src/app/questions/questions.component.ts new file mode 100644 index 0000000000..41e6d1d1ff --- /dev/null +++ b/frontend/src/app/questions/questions.component.ts @@ -0,0 +1,167 @@ +import { Component, OnInit } from '@angular/core'; +import { TableModule } from 'primeng/table'; +import { FormsModule } from '@angular/forms'; +import { ToastModule } from 'primeng/toast'; +import { ToolbarModule } from 'primeng/toolbar'; +import { ButtonModule } from 'primeng/button'; +import { CommonModule, NgFor } from '@angular/common'; +import { ConfirmationService, MessageService } from 'primeng/api'; +import { ConfirmDialogModule } from 'primeng/confirmdialog'; +import { DialogModule } from 'primeng/dialog'; +import { MultiSelectModule } from 'primeng/multiselect'; +import { ReactiveFormsModule } from '@angular/forms'; +import { DropdownModule } from 'primeng/dropdown'; +import { ProgressSpinnerModule } from 'primeng/progressspinner'; +import { Question } from './question.model'; +import { QuestionService } from '../../_services/question.service'; +import { forkJoin } from 'rxjs'; +import { HttpErrorResponse } from '@angular/common/http'; +import { QuestionDialogComponent } from './question-dialog.component'; +import { Column } from './column.model'; + +@Component({ + selector: 'app-questions', + standalone: true, + imports: [ + TableModule, + NgFor, + CommonModule, + FormsModule, + ToastModule, + ToolbarModule, + ButtonModule, + ConfirmDialogModule, + DialogModule, + ButtonModule, + ReactiveFormsModule, + MultiSelectModule, + DropdownModule, + ProgressSpinnerModule, + QuestionDialogComponent, + ], + providers: [QuestionService, ConfirmationService, MessageService], + templateUrl: './questions.component.html', + styleUrl: './questions.component.css', +}) +export class QuestionsComponent implements OnInit { + loading = true; + + questions: Question[] = []; + + cols: Column[] = []; + + question!: Question; + + selectedQuestions!: Question[] | null; + + isDialogVisible = false; + + constructor( + private questionService: QuestionService, + private messageService: MessageService, + private confirmationService: ConfirmationService, + ) {} + + ngOnInit() { + // two way binding for forms is not working for some reason unless question is initialised with empty values + this.initQuestion(); + + // fetch data from API call + this.handleInitData(); + } + + openNewQuestion() { + this.question = {} as Question; + this.isDialogVisible = true; + } + + editQuestion(question: Question) { + this.question = question; + this.isDialogVisible = true; + } + + deleteSelectedQuestions() { + this.confirmationService.confirm({ + message: 'Are you sure you want to delete the selected questions?', + header: 'Delete Confirmation', + icon: 'pi pi-exclamation-triangle', + accept: () => { + this.handleDeleteQuestionResponse(); + }, + }); + } + + initQuestion() { + this.question = { + id: -1, + title: '', + topics: [], + description: '', + difficulty: '', + }; + } + + handleDeleteQuestionResponse() { + const deleteRequests = this.selectedQuestions?.map(q => this.questionService.deleteQuestion(q.id)); + + forkJoin(deleteRequests!).subscribe({ + next: () => { + // delete locally + this.questions = this.questions?.filter(val => !this.selectedQuestions?.includes(val)); + this.selectedQuestions = null; + }, + error: (error: HttpErrorResponse) => { + this.onErrorReceive('Some questions could not be deleted. ' + error.error.message); + }, + complete: () => { + this.onSuccessfulRequest('Question(s) Deleted'); + }, + }); + } + + handleInitData() { + this.questionService.getQuestions().subscribe({ + next: response => { + this.questions = response.data || []; + }, + error: () => { + this.questions = []; + this.onErrorReceive('Failed to load data. Please try again later.'); + }, + complete: () => { + this.loading = false; + }, + }); + } + + onDialogClose() { + this.isDialogVisible = false; + } + + onQuestionUpdate(question: Question) { + this.questions[this.questions.findIndex(x => x.id == question.id)] = question; + this.questions = [...this.questions]; + } + + onQuestionAdd(question: Question) { + this.questions = [...this.questions, question]; + } + + onErrorReceive(errorMessage: string) { + this.messageService.add({ + severity: 'error', + summary: 'Error', + detail: errorMessage, + life: 3000, + }); + } + + onSuccessfulRequest(successMessage: string) { + this.messageService.add({ + severity: 'success', + summary: 'Successful', + detail: successMessage, + life: 3000, + }); + } +} diff --git a/frontend/src/app/questions/topic.model.ts b/frontend/src/app/questions/topic.model.ts new file mode 100644 index 0000000000..2a4849883a --- /dev/null +++ b/frontend/src/app/questions/topic.model.ts @@ -0,0 +1,10 @@ +export interface Topic { + label: string; + value: string; +} + +export interface TopicResponse { + status: string; + message: string; + data?: string[] | null; +} diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 27aea93c1a..077656db39 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -1,7 +1,8 @@ /* You can add global styles to this file, and also import other style files */ @import "primeng/resources/themes/aura-dark-blue/theme.css"; -@import "primeng/resources/primeng.css"; -@import "primeflex/primeflex.scss"; +@import "primeng/resources/primeng.min.css"; +@import "primeflex/primeflex.min.css"; +@import "primeicons/primeicons.css"; body { height: 100vh; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000000..e7f5318afc --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "PeerPrep", + "lockfileVersion": 3, + "requires": true, + "packages": {} +}