mirror of
https://github.com/akveo/ngx-admin.git
synced 2025-12-16 15:40:11 +01:00
Merge fa88d3ee0c into 06776d15c4
This commit is contained in:
commit
8b7150b6b2
41 changed files with 33037 additions and 676 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -40,3 +40,5 @@ testem.log
|
|||
# System Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
/.angular
|
||||
|
|
|
|||
|
|
@ -187,5 +187,8 @@
|
|||
"@angular-eslint/schematics:library": {
|
||||
"setParserOptionsProject": true
|
||||
}
|
||||
},
|
||||
"cli": {
|
||||
"analytics": false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
31529
package-lock.json
generated
31529
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -35,14 +35,16 @@
|
|||
"@angular/common": "^15.2.10",
|
||||
"@angular/compiler": "^15.2.10",
|
||||
"@angular/core": "^15.2.10",
|
||||
"@angular/fire": "^7.6.1",
|
||||
"@angular/forms": "^15.2.10",
|
||||
"@angular/google-maps": "^12.2.13",
|
||||
"@angular/platform-browser": "^15.2.10",
|
||||
"@angular/platform-browser-dynamic": "^15.2.10",
|
||||
"@angular/router": "^15.2.10",
|
||||
"@asymmetrik/ngx-leaflet": "3.0.1",
|
||||
"@nebular/auth": "11.0.1",
|
||||
"@nebular/auth": "^11.0.1",
|
||||
"@nebular/eva-icons": "11.0.1",
|
||||
"@nebular/firebase-auth": "^13.0.0",
|
||||
"@nebular/security": "11.0.1",
|
||||
"@nebular/theme": "11.0.1",
|
||||
"@swimlane/ngx-charts": "^14.0.0",
|
||||
|
|
@ -62,12 +64,12 @@
|
|||
"ng2-completer": "^9.0.1",
|
||||
"ng2-smart-table": "^1.6.0",
|
||||
"ngx-echarts": "^4.2.2",
|
||||
"node-sass": "^4.14.1",
|
||||
"normalize.css": "6.0.0",
|
||||
"pace-js": "1.0.2",
|
||||
"roboto-fontface": "0.8.0",
|
||||
"rxjs": "6.6.2",
|
||||
"rxjs-compat": "6.3.0",
|
||||
"sass": "^1.49.0",
|
||||
"socicon": "3.0.5",
|
||||
"style-loader": "^1.3.0",
|
||||
"tinymce": "4.5.7",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { of as observableOf, Observable } from 'rxjs';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable, of as observableOf } from 'rxjs';
|
||||
import { Contacts, RecentUsers, UserData } from '../data/users';
|
||||
|
||||
@Injectable()
|
||||
|
|
@ -8,7 +8,7 @@ export class UserService extends UserData {
|
|||
private time: Date = new Date;
|
||||
|
||||
private users = {
|
||||
nick: { name: 'Nick Jones', picture: 'assets/images/nick.png' },
|
||||
nick: { name: 'Users', picture: 'assets/images/nick.png' },
|
||||
eva: { name: 'Eva Moor', picture: 'assets/images/eva.png' },
|
||||
jack: { name: 'Jack Williams', picture: 'assets/images/jack.png' },
|
||||
lee: { name: 'Lee Wong', picture: 'assets/images/lee.png' },
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { Component } from '@angular/core';
|
|||
styleUrls: ['./footer.component.scss'],
|
||||
template: `
|
||||
<span class="created-by">
|
||||
Created with ♥ by <b><a href="https://akveo.page.link/8V2f" target="_blank">Akveo</a></b> 2019
|
||||
|
||||
</span>
|
||||
<div class="socials">
|
||||
<a href="#" target="_blank" class="ion ion-social-github"></a>
|
||||
|
|
|
|||
|
|
@ -3,26 +3,37 @@
|
|||
<a (click)="toggleSidebar()" href="#" class="sidebar-toggle">
|
||||
<nb-icon icon="menu-2-outline"></nb-icon>
|
||||
</a>
|
||||
<a class="logo" href="#" (click)="navigateHome()">ngx-<span>admin</span></a>
|
||||
<a class="logo" href="#" (click)="navigateHome()"
|
||||
><span><img src="assets/images/thread.png" alt="My Logo" /></span>Resume
|
||||
Tailor</a
|
||||
>
|
||||
</div>
|
||||
<nb-select [selected]="currentTheme" (selectedChange)="changeTheme($event)" status="primary">
|
||||
<nb-option *ngFor="let theme of themes" [value]="theme.value"> {{ theme.name }}</nb-option>
|
||||
<div class="spc"></div>
|
||||
<nb-select
|
||||
[selected]="currentTheme"
|
||||
(selectedChange)="changeTheme($event)"
|
||||
status="primary"
|
||||
>
|
||||
<nb-option *ngFor="let theme of themes" [value]="theme.value">
|
||||
{{ theme.name }}</nb-option
|
||||
>
|
||||
</nb-select>
|
||||
</div>
|
||||
|
||||
<div class="header-container">
|
||||
<nb-actions size="small">
|
||||
|
||||
<nb-action class="control-item">
|
||||
<nb-search type="rotate-layout"></nb-search>
|
||||
</nb-action>
|
||||
<nb-action class="control-item" icon="email-outline"></nb-action>
|
||||
<nb-action class="control-item" icon="bell-outline"></nb-action>
|
||||
<nb-action class="user-action" *nbIsGranted="['view', 'user']" >
|
||||
<nb-user [nbContextMenu]="userMenu"
|
||||
[onlyPicture]="userPictureOnly"
|
||||
[name]="user?.name"
|
||||
[picture]="user?.picture">
|
||||
<nb-action class="user-action" *nbIsGranted="['view', 'user']">
|
||||
<nb-user
|
||||
[nbContextMenu]="userMenu"
|
||||
[onlyPicture]="userPictureOnly"
|
||||
[name]="user?.name"
|
||||
[picture]="user?.picture"
|
||||
>
|
||||
</nb-user>
|
||||
</nb-action>
|
||||
</nb-actions>
|
||||
|
|
|
|||
|
|
@ -50,6 +50,14 @@
|
|||
white-space: nowrap;
|
||||
text-decoration: none;
|
||||
}
|
||||
.spc {
|
||||
padding-left: 100px;
|
||||
font-size: 1.25rem;
|
||||
@include nb-ltr(border-left, 1px solid nb-theme(divider-color));
|
||||
@include nb-rtl(border-right, 1px solid nb-theme(divider-color));
|
||||
white-space: nowrap;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { NbMediaBreakpointsService, NbMenuService, NbSidebarService, NbThemeService } from '@nebular/theme';
|
||||
import { NbMediaBreakpointsService, NbMenuService, NbSidebarService, NbThemeService, NbMenuItem } from '@nebular/theme';
|
||||
|
||||
import { UserData } from '../../../@core/data/users';
|
||||
import { LayoutService } from '../../../@core/utils';
|
||||
import { map, takeUntil } from 'rxjs/operators';
|
||||
import { Subject } from 'rxjs';
|
||||
import { AuthService } from '../../../service/auth.service';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-header',
|
||||
|
|
@ -26,14 +28,14 @@ export class HeaderComponent implements OnInit, OnDestroy {
|
|||
value: 'dark',
|
||||
name: 'Dark',
|
||||
},
|
||||
{
|
||||
value: 'cosmic',
|
||||
name: 'Cosmic',
|
||||
},
|
||||
{
|
||||
value: 'corporate',
|
||||
name: 'Corporate',
|
||||
},
|
||||
// {
|
||||
// value: 'cosmic',
|
||||
// name: 'Cosmic',
|
||||
// },
|
||||
// {
|
||||
// value: 'corporate',
|
||||
// name: 'Corporate',
|
||||
// },
|
||||
];
|
||||
|
||||
currentTheme = 'default';
|
||||
|
|
@ -45,7 +47,9 @@ export class HeaderComponent implements OnInit, OnDestroy {
|
|||
private themeService: NbThemeService,
|
||||
private userService: UserData,
|
||||
private layoutService: LayoutService,
|
||||
private breakpointService: NbMediaBreakpointsService) {
|
||||
private breakpointService: NbMediaBreakpointsService,
|
||||
private authService: AuthService,
|
||||
private router: Router) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
|
|
@ -69,6 +73,14 @@ export class HeaderComponent implements OnInit, OnDestroy {
|
|||
takeUntil(this.destroy$),
|
||||
)
|
||||
.subscribe(themeName => this.currentTheme = themeName);
|
||||
|
||||
this.menuService.onItemClick().subscribe((event: { item: NbMenuItem }) => {
|
||||
if (event.item.title === 'Log out') {
|
||||
this.logout();
|
||||
} else if (event.item.title === 'Profile') {
|
||||
this.router.navigate(['/pages/layout/stepper/profile']); // Redirect to profile page
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
|
|
@ -91,4 +103,8 @@ export class HeaderComponent implements OnInit, OnDestroy {
|
|||
this.menuService.navigateHome();
|
||||
return false;
|
||||
}
|
||||
|
||||
logout(): void {
|
||||
this.authService.logout();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,51 +1,18 @@
|
|||
import { ExtraOptions, RouterModule, Routes } from '@angular/router';
|
||||
import { NgModule } from '@angular/core';
|
||||
import {
|
||||
NbAuthComponent,
|
||||
NbLoginComponent,
|
||||
NbLogoutComponent,
|
||||
NbRegisterComponent,
|
||||
NbRequestPasswordComponent,
|
||||
NbResetPasswordComponent,
|
||||
} from '@nebular/auth';
|
||||
import { ExtraOptions, RouterModule, Routes } from '@angular/router';
|
||||
import { AuthGuard } from './service/auth-guard.service';
|
||||
|
||||
export const routes: Routes = [
|
||||
{
|
||||
path: 'pages',
|
||||
canActivate: [AuthGuard],
|
||||
loadChildren: () => import('./pages/pages.module')
|
||||
.then(m => m.PagesModule),
|
||||
},
|
||||
{
|
||||
path: 'auth',
|
||||
component: NbAuthComponent,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: NbLoginComponent,
|
||||
},
|
||||
{
|
||||
path: 'login',
|
||||
component: NbLoginComponent,
|
||||
},
|
||||
{
|
||||
path: 'register',
|
||||
component: NbRegisterComponent,
|
||||
},
|
||||
{
|
||||
path: 'logout',
|
||||
component: NbLogoutComponent,
|
||||
},
|
||||
{
|
||||
path: 'request-password',
|
||||
component: NbRequestPasswordComponent,
|
||||
},
|
||||
{
|
||||
path: 'reset-password',
|
||||
component: NbResetPasswordComponent,
|
||||
},
|
||||
],
|
||||
loadChildren: () => import('./auth/auth.module').then(m => m.NgxAuthModule)
|
||||
},
|
||||
{ path: '', redirectTo: 'pages', pathMatch: 'full' },
|
||||
{ path: '**', redirectTo: 'pages' },
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,8 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Akveo. All Rights Reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*/
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { AngularFireModule } from '@angular/fire/compat';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { CoreModule } from './@core/core.module';
|
||||
import { ThemeModule } from './@theme/theme.module';
|
||||
import { AppComponent } from './app.component';
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import {
|
||||
NbChatModule,
|
||||
NbDatepickerModule,
|
||||
|
|
@ -19,7 +11,16 @@ import {
|
|||
NbSidebarModule,
|
||||
NbToastrModule,
|
||||
NbWindowModule,
|
||||
NbCheckboxModule
|
||||
} from '@nebular/theme';
|
||||
import { environment } from '../environments/environment';
|
||||
import { CoreModule } from './@core/core.module';
|
||||
import { ThemeModule } from './@theme/theme.module';
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent } from './app.component';
|
||||
import { AngularFireAuthModule } from '@angular/fire/compat/auth';
|
||||
|
||||
import { AuthGuard } from './service/auth-guard.service';
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent],
|
||||
|
|
@ -28,6 +29,8 @@ import {
|
|||
BrowserAnimationsModule,
|
||||
HttpClientModule,
|
||||
AppRoutingModule,
|
||||
AngularFireModule.initializeApp(environment.firebase),
|
||||
AngularFireAuthModule,
|
||||
NbSidebarModule.forRoot(),
|
||||
NbMenuModule.forRoot(),
|
||||
NbDatepickerModule.forRoot(),
|
||||
|
|
@ -39,6 +42,10 @@ import {
|
|||
}),
|
||||
CoreModule.forRoot(),
|
||||
ThemeModule.forRoot(),
|
||||
NbCheckboxModule
|
||||
],
|
||||
providers: [
|
||||
AuthGuard
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
|
|
|
|||
72
src/app/auth/auth-firebase.config.ts
Normal file
72
src/app/auth/auth-firebase.config.ts
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import { NbAuthStrategyOptions, NbAuthJWTToken, NbPasswordStrategyModule, NbPasswordStrategyMessage } from "@nebular/auth";
|
||||
|
||||
export class NbFirebasePasswordStrategyOptions extends NbAuthStrategyOptions {
|
||||
name: string = 'email';
|
||||
token = {
|
||||
class: NbAuthJWTToken,
|
||||
};
|
||||
register?: boolean | NbPasswordStrategyModule = {
|
||||
redirect: {
|
||||
success: '/auth/login',
|
||||
failure: null,
|
||||
},
|
||||
defaultErrors: ['Something went wrong, please try again.'],
|
||||
defaultMessages: ['You have been successfully registered.'],
|
||||
};
|
||||
login?: boolean | NbPasswordStrategyModule = {
|
||||
redirect: {
|
||||
success: '/dashboard',
|
||||
failure: null,
|
||||
},
|
||||
defaultErrors: ['Login/Email combination is not correct, please try again.'],
|
||||
defaultMessages: ['You have been successfully logged in.'],
|
||||
};
|
||||
logout?: boolean | NbPasswordStrategyModule = {
|
||||
redirect: {
|
||||
success: '/',
|
||||
failure: null,
|
||||
},
|
||||
defaultErrors: ['Something went wrong, please try again.'],
|
||||
defaultMessages: ['You have been successfully logged out.'],
|
||||
};
|
||||
refreshToken?: boolean | NbPasswordStrategyModule = {
|
||||
redirect: {
|
||||
success: null,
|
||||
failure: null,
|
||||
},
|
||||
defaultErrors: ['Something went wrong, please try again.'],
|
||||
defaultMessages: ['Your token has been successfully refreshed.'],
|
||||
};
|
||||
// requestPassword?: boolean | NbPasswordStrategyModule = {
|
||||
// redirect: {
|
||||
// success: '/',
|
||||
// failure: null,
|
||||
// },
|
||||
// defaultErrors: ['Something went wrong, please try again.'],
|
||||
// defaultMessages: ['Reset password instructions have been sent to your email.'],
|
||||
// };
|
||||
// resetPassword?: boolean | NbPasswordStrategyModule = {
|
||||
// redirect: {
|
||||
// success: '/',
|
||||
// failure: null,
|
||||
// },
|
||||
// defaultErrors: ['Something went wrong, please try again.'],
|
||||
// defaultMessages: ['Your password has been successfully changed.'],
|
||||
// };
|
||||
// errors?: NbPasswordStrategyMessage = {
|
||||
// key: 'message',
|
||||
// getter: (module: string, res, options: NbFirebasePasswordStrategyOptions) => getDeepFromObject(
|
||||
// res,
|
||||
// options.errors.key,
|
||||
// options[module].defaultErrors,
|
||||
// ),
|
||||
// };
|
||||
// messages?: NbPasswordStrategyMessage = {
|
||||
// key: 'messages',
|
||||
// getter: (module: string, res, options: NbFirebasePasswordStrategyOptions) => getDeepFromObject(
|
||||
// res.body,
|
||||
// options.messages.key,
|
||||
// options[module].defaultMessages,
|
||||
// ),
|
||||
// };
|
||||
}
|
||||
31
src/app/auth/auth-routing.module.ts
Normal file
31
src/app/auth/auth-routing.module.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { NbAuthComponent } from '@nebular/auth';
|
||||
import { NgxLoginComponent } from './login/login.component';
|
||||
import { NgxRegisterComponent } from './register/register.component';
|
||||
|
||||
export const routes: Routes = [
|
||||
// .. here goes our components routes
|
||||
{
|
||||
path: '',
|
||||
component: NbAuthComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'login',
|
||||
component: NgxLoginComponent,
|
||||
},
|
||||
{
|
||||
path: 'register',
|
||||
component: NgxRegisterComponent,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class NgxAuthRoutingModule {
|
||||
}
|
||||
61
src/app/auth/auth.module.ts
Normal file
61
src/app/auth/auth.module.ts
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { NgxAuthRoutingModule } from './auth-routing.module';
|
||||
import { NbAuthModule, NbPasswordAuthStrategy } from '@nebular/auth';
|
||||
import {
|
||||
NbAlertModule,
|
||||
NbButtonModule,
|
||||
NbCheckboxModule,
|
||||
NbInputModule
|
||||
} from '@nebular/theme';
|
||||
import { NgxLoginComponent } from './login/login.component';
|
||||
import { NgxRegisterComponent } from './register/register.component';
|
||||
import { NbFirebasePasswordStrategyOptions } from './auth-firebase.config';
|
||||
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
RouterModule,
|
||||
NbAlertModule,
|
||||
NbInputModule,
|
||||
NbButtonModule,
|
||||
NbCheckboxModule,
|
||||
NgxAuthRoutingModule,
|
||||
|
||||
NbAuthModule.forRoot({
|
||||
strategies: [
|
||||
NbPasswordAuthStrategy.setup({
|
||||
name: 'email',
|
||||
|
||||
login: {
|
||||
redirect: {
|
||||
success: '/dashboard',
|
||||
failure: null, // stay on the same page
|
||||
},
|
||||
},
|
||||
|
||||
register: {
|
||||
redirect: {
|
||||
success: '/auth/login',
|
||||
failure: null, // stay on the same page
|
||||
},
|
||||
},
|
||||
...new NbFirebasePasswordStrategyOptions()
|
||||
}),
|
||||
],
|
||||
forms: {},
|
||||
}),
|
||||
],
|
||||
declarations: [
|
||||
// ... here goes our new components
|
||||
NgxLoginComponent,
|
||||
NgxRegisterComponent
|
||||
],
|
||||
})
|
||||
export class NgxAuthModule {
|
||||
}
|
||||
159
src/app/auth/login/login.component.html
Normal file
159
src/app/auth/login/login.component.html
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
<h1 id="title" class="title">Login</h1>
|
||||
<p class="sub-title">Welcome to RESUME TAILOR!!!</p>
|
||||
|
||||
<!-- <nb-alert
|
||||
*ngIf="showMessages.error && errors?.length && !submitted"
|
||||
outline="danger"
|
||||
role="alert"
|
||||
>
|
||||
<p class="alert-title"><b>Oh snap!</b></p>
|
||||
<ul class="alert-message-list">
|
||||
<li *ngFor="let error of errors" class="alert-message">{{ error }}</li>
|
||||
</ul>
|
||||
</nb-alert> -->
|
||||
|
||||
<!-- <nb-alert
|
||||
*ngIf="showMessages.success && messages?.length && !submitted"
|
||||
outline="success"
|
||||
role="alert"
|
||||
>
|
||||
<p class="alert-title"><b>Hooray!</b></p>
|
||||
<ul class="alert-message-list">
|
||||
<li *ngFor="let message of messages" class="alert-message">
|
||||
{{ message }}
|
||||
</li>
|
||||
</ul>
|
||||
</nb-alert> -->
|
||||
|
||||
<form (ngSubmit)="login()" #form="ngForm" aria-labelledby="title">
|
||||
<div class="form-control-group">
|
||||
<label class="label" for="input-email">Email address:</label>
|
||||
<input
|
||||
nbInput
|
||||
fullWidth
|
||||
[(ngModel)]="user.email"
|
||||
#email="ngModel"
|
||||
name="email"
|
||||
id="input-email"
|
||||
pattern=".+@.+\..+"
|
||||
placeholder="Email address"
|
||||
fieldSize="large"
|
||||
autofocus
|
||||
[status]="email.dirty ? (email.invalid ? 'danger' : 'success') : 'basic'"
|
||||
[required]="getConfigValue('forms.validation.email.required')"
|
||||
[attr.aria-invalid]="email.invalid && email.touched ? true : null"
|
||||
/>
|
||||
<ng-container *ngIf="email.invalid && email.touched">
|
||||
<p class="caption status-danger" *ngIf="email.errors?.required">
|
||||
Email is required!
|
||||
</p>
|
||||
<p class="caption status-danger" *ngIf="email.errors?.pattern">
|
||||
Email should be the real one!
|
||||
</p>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="form-control-group">
|
||||
<span class="label-with-link">
|
||||
<label class="label" for="input-password">Password:</label>
|
||||
<!-- <a class="forgot-password caption-2" routerLink="../request-password"
|
||||
>Forgot Password?</a
|
||||
> -->
|
||||
</span>
|
||||
<input
|
||||
nbInput
|
||||
fullWidth
|
||||
[(ngModel)]="user.password"
|
||||
#password="ngModel"
|
||||
name="password"
|
||||
type="password"
|
||||
id="input-password"
|
||||
placeholder="Password"
|
||||
fieldSize="large"
|
||||
[status]="
|
||||
password.dirty ? (password.invalid ? 'danger' : 'success') : 'basic'
|
||||
"
|
||||
[required]="getConfigValue('forms.validation.password.required')"
|
||||
[minlength]="getConfigValue('forms.validation.password.minLength')"
|
||||
[maxlength]="getConfigValue('forms.validation.password.maxLength')"
|
||||
[attr.aria-invalid]="password.invalid && password.touched ? true : null"
|
||||
/>
|
||||
<ng-container *ngIf="password.invalid && password.touched">
|
||||
<p class="caption status-danger" *ngIf="password.errors?.required">
|
||||
Password is required!
|
||||
</p>
|
||||
<p
|
||||
class="caption status-danger"
|
||||
*ngIf="password.errors?.minlength || password.errors?.maxlength"
|
||||
>
|
||||
Password should contain from
|
||||
{{ getConfigValue("forms.validation.password.minLength") }} to
|
||||
{{ getConfigValue("forms.validation.password.maxLength") }}
|
||||
characters
|
||||
</p>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="form-control-group accept-group">
|
||||
<nb-checkbox
|
||||
name="rememberMe"
|
||||
[(ngModel)]="user.rememberMe"
|
||||
*ngIf="rememberMe"
|
||||
>Remember me</nb-checkbox
|
||||
>
|
||||
</div>
|
||||
|
||||
<button
|
||||
nbButton
|
||||
fullWidth
|
||||
status="primary"
|
||||
size="large"
|
||||
[disabled]="submitted || !form.valid"
|
||||
[class.btn-pulse]="submitted"
|
||||
>
|
||||
Log In
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- <section
|
||||
*ngIf="socialLinks && socialLinks.length > 0"
|
||||
class="links"
|
||||
aria-label="Social sign in"
|
||||
>
|
||||
or enter with:
|
||||
<div class="socials">
|
||||
<ng-container *ngFor="let socialLink of socialLinks">
|
||||
<a
|
||||
*ngIf="socialLink.link"
|
||||
[routerLink]="socialLink.link"
|
||||
[attr.target]="socialLink.target"
|
||||
[attr.class]="socialLink.icon"
|
||||
[class.with-icon]="socialLink.icon"
|
||||
>
|
||||
<nb-icon
|
||||
*ngIf="socialLink.icon; else title"
|
||||
[icon]="socialLink.icon"
|
||||
></nb-icon>
|
||||
<ng-template #title>{{ socialLink.title }}</ng-template>
|
||||
</a>
|
||||
<a
|
||||
*ngIf="socialLink.url"
|
||||
[attr.href]="socialLink.url"
|
||||
[attr.target]="socialLink.target"
|
||||
[attr.class]="socialLink.icon"
|
||||
[class.with-icon]="socialLink.icon"
|
||||
>
|
||||
<nb-icon
|
||||
*ngIf="socialLink.icon; else title"
|
||||
[icon]="socialLink.icon"
|
||||
></nb-icon>
|
||||
<ng-template #title>{{ socialLink.title }}</ng-template>
|
||||
</a>
|
||||
</ng-container>
|
||||
</div>
|
||||
</section> -->
|
||||
|
||||
<section class="another-action" aria-label="Register">
|
||||
Don't have an account?
|
||||
<a class="text-link" routerLink="../register">Register</a>
|
||||
</section>
|
||||
71
src/app/auth/login/login.component.ts
Normal file
71
src/app/auth/login/login.component.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import { Component, ChangeDetectorRef } from '@angular/core';
|
||||
import { AngularFireAuth } from '@angular/fire/compat/auth';
|
||||
import { Router } from '@angular/router'; // Import Router from @angular/router
|
||||
import { NbAuthService } from '@nebular/auth';
|
||||
import { NbLoginComponent } from '@nebular/auth';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-login',
|
||||
templateUrl: './login.component.html',
|
||||
})
|
||||
export class NgxLoginComponent extends NbLoginComponent {
|
||||
constructor(
|
||||
private fireAuth: AngularFireAuth,
|
||||
private authService: NbAuthService,
|
||||
public cd: ChangeDetectorRef, // Change to public or protected if NbLoginComponent's cd is public or protected
|
||||
public router: Router // Import Router from @angular/router
|
||||
) {
|
||||
super(authService, {}, cd, router);
|
||||
}
|
||||
|
||||
async login() {
|
||||
try {
|
||||
await this.fireAuth.signInWithEmailAndPassword(this.user.email, this.user.password);
|
||||
const user = await this.fireAuth.currentUser; // Get current user
|
||||
if (user) {
|
||||
const accessToken = await user.getIdToken(); // Get Firebase Authentication token (access token)
|
||||
const expiresIn = new Date().getTime() + 3600 * 1000; // Token expiration time (1 hour)
|
||||
localStorage.setItem('accessToken', accessToken);
|
||||
localStorage.setItem('accessTokenExpiresIn', expiresIn.toString()); // Store token expiration time
|
||||
this.router.navigate(['dashboard']); // Redirect to dashboard
|
||||
this.scheduleTokenRefresh(); // Schedule token refresh
|
||||
}
|
||||
console.log("Successfully Logged in.")
|
||||
} catch (error) {
|
||||
// Handle login errors here
|
||||
console.error('Error signing in:', error);
|
||||
// You can also display error messages to the user using this.errors array
|
||||
this.errors = ['Error signing in. Please try again.'];
|
||||
}
|
||||
}
|
||||
|
||||
private async scheduleTokenRefresh(): Promise<void> {
|
||||
const expiresInString = localStorage.getItem('accessTokenExpiresIn');
|
||||
if (expiresInString) {
|
||||
const expiresIn = +expiresInString;
|
||||
const timeUntilRefresh = expiresIn - new Date().getTime() - 300000; // Refresh token 5 minutes before expiration
|
||||
if (timeUntilRefresh > 0) {
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
const user = await this.fireAuth.currentUser;
|
||||
if (user) {
|
||||
const newAccessToken = await user.getIdToken();
|
||||
localStorage.setItem('accessToken', newAccessToken);
|
||||
const newExpiresIn = new Date().getTime() + 3600 * 1000; // Extend expiration time by 1 hour
|
||||
localStorage.setItem('accessTokenExpiresIn', newExpiresIn.toString());
|
||||
this.scheduleTokenRefresh();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Token refresh failed:', error);
|
||||
}
|
||||
}, timeUntilRefresh);
|
||||
}
|
||||
} else {
|
||||
console.error('Access token expiration time is not available.');
|
||||
}
|
||||
}
|
||||
|
||||
getConfigValue(key: string): any {
|
||||
// Implement this method based on your configuration retrieval logic
|
||||
}
|
||||
}
|
||||
227
src/app/auth/register/register.component.html
Normal file
227
src/app/auth/register/register.component.html
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
<h1 id="title" class="title">Register</h1>
|
||||
|
||||
<!-- <nb-alert
|
||||
*ngIf="showMessages.error && errors?.length && !submitted"
|
||||
outline="danger"
|
||||
role="alert"
|
||||
>
|
||||
<p class="alert-title"><b>Oh snap!</b></p>
|
||||
<ul class="alert-message-list">
|
||||
<li *ngFor="let error of errors" class="alert-message">{{ error }}</li>
|
||||
</ul>
|
||||
</nb-alert>
|
||||
|
||||
<nb-alert
|
||||
*ngIf="showMessages.success && messages?.length && !submitted"
|
||||
outline="success"
|
||||
role="alert"
|
||||
>
|
||||
<p class="alert-title"><b>Hooray!</b></p>
|
||||
<ul class="alert-message-list">
|
||||
<li *ngFor="let message of messages" class="alert-message">
|
||||
{{ message }}
|
||||
</li>
|
||||
</ul>
|
||||
</nb-alert> -->
|
||||
|
||||
<form (ngSubmit)="register()" #form="ngForm" aria-labelledby="title">
|
||||
<div class="form-control-group">
|
||||
<label class="label" for="input-name">Full name:</label>
|
||||
<input
|
||||
nbInput
|
||||
[(ngModel)]="user.fullName"
|
||||
#fullName="ngModel"
|
||||
id="input-name"
|
||||
name="fullName"
|
||||
placeholder="Full name"
|
||||
autofocus
|
||||
fullWidth
|
||||
fieldSize="large"
|
||||
[status]="
|
||||
fullName.dirty ? (fullName.invalid ? 'danger' : 'success') : 'basic'
|
||||
"
|
||||
[required]="getConfigValue('forms.validation.fullName.required')"
|
||||
[minlength]="getConfigValue('forms.validation.fullName.minLength')"
|
||||
[maxlength]="getConfigValue('forms.validation.fullName.maxLength')"
|
||||
[attr.aria-invalid]="fullName.invalid && fullName.touched ? true : null"
|
||||
/>
|
||||
<ng-container *ngIf="fullName.invalid && fullName.touched">
|
||||
<p class="caption status-danger" *ngIf="fullName.errors?.required">
|
||||
Full name is required!
|
||||
</p>
|
||||
<p
|
||||
class="caption status-danger"
|
||||
*ngIf="fullName.errors?.minlength || fullName.errors?.maxlength"
|
||||
>
|
||||
Full name should contains from
|
||||
{{ getConfigValue("forms.validation.fullName.minLength") }} to
|
||||
{{ getConfigValue("forms.validation.fullName.maxLength") }}
|
||||
characters
|
||||
</p>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="form-control-group">
|
||||
<label class="label" for="input-email">Email address:</label>
|
||||
<input
|
||||
nbInput
|
||||
[(ngModel)]="user.email"
|
||||
#email="ngModel"
|
||||
id="input-email"
|
||||
name="email"
|
||||
pattern=".+@.+..+"
|
||||
placeholder="Email address"
|
||||
fullWidth
|
||||
fieldSize="large"
|
||||
[status]="email.dirty ? (email.invalid ? 'danger' : 'success') : 'basic'"
|
||||
[required]="getConfigValue('forms.validation.email.required')"
|
||||
[attr.aria-invalid]="email.invalid && email.touched ? true : null"
|
||||
/>
|
||||
<ng-container *ngIf="email.invalid && email.touched">
|
||||
<p class="caption status-danger" *ngIf="email.errors?.required">
|
||||
Email is required!
|
||||
</p>
|
||||
<p class="caption status-danger" *ngIf="email.errors?.pattern">
|
||||
Email should be the real one!
|
||||
</p>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="form-control-group">
|
||||
<label class="label" for="input-password">Password:</label>
|
||||
<input
|
||||
nbInput
|
||||
[(ngModel)]="user.password"
|
||||
#password="ngModel"
|
||||
type="password"
|
||||
id="input-password"
|
||||
name="password"
|
||||
placeholder="Password"
|
||||
fullWidth
|
||||
fieldSize="large"
|
||||
[status]="
|
||||
password.dirty ? (password.invalid ? 'danger' : 'success') : 'basic'
|
||||
"
|
||||
[required]="getConfigValue('forms.validation.password.required')"
|
||||
[minlength]="getConfigValue('forms.validation.password.minLength')"
|
||||
[maxlength]="getConfigValue('forms.validation.password.maxLength')"
|
||||
[attr.aria-invalid]="password.invalid && password.touched ? true : null"
|
||||
/>
|
||||
<ng-container *ngIf="password.invalid && password.touched">
|
||||
<p class="caption status-danger" *ngIf="password.errors?.required">
|
||||
Password is required!
|
||||
</p>
|
||||
<p
|
||||
class="caption status-danger"
|
||||
*ngIf="password.errors?.minlength || password.errors?.maxlength"
|
||||
>
|
||||
Password should contain from
|
||||
{{ getConfigValue("forms.validation.password.minLength") }} to
|
||||
{{ getConfigValue("forms.validation.password.maxLength") }}
|
||||
characters
|
||||
</p>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="form-control-group">
|
||||
<label class="label" for="input-re-password">Confirm password:</label>
|
||||
<input
|
||||
nbInput
|
||||
[(ngModel)]="user.confirmPassword"
|
||||
#rePass="ngModel"
|
||||
type="password"
|
||||
id="input-re-password"
|
||||
name="rePass"
|
||||
placeholder="Confirm Password"
|
||||
fullWidth
|
||||
fieldSize="large"
|
||||
[status]="
|
||||
rePass.dirty
|
||||
? rePass.invalid || password.value != rePass.value
|
||||
? 'danger'
|
||||
: 'success'
|
||||
: 'basic'
|
||||
"
|
||||
[required]="getConfigValue('forms.validation.password.required')"
|
||||
[attr.aria-invalid]="rePass.invalid && rePass.touched ? true : null"
|
||||
/>
|
||||
<ng-container *ngIf="rePass.invalid && rePass.touched">
|
||||
<p class="caption status-danger" *ngIf="rePass.errors?.required">
|
||||
Password confirmation is required!
|
||||
</p>
|
||||
<p
|
||||
class="caption status-danger"
|
||||
*ngIf="password.value != rePass.value && !rePass.errors?.required"
|
||||
>
|
||||
Password does not match the confirm password.
|
||||
</p>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<!-- <div
|
||||
class="form-control-group accept-group"
|
||||
*ngIf="getConfigValue('forms.register.terms')"
|
||||
>
|
||||
<nb-checkbox
|
||||
name="terms"
|
||||
[(ngModel)]="user.terms"
|
||||
[required]="getConfigValue('forms.register.terms')"
|
||||
>
|
||||
Agree to
|
||||
<a href="#" target="_blank"><strong>Terms & Conditions</strong></a>
|
||||
</nb-checkbox>
|
||||
</div> -->
|
||||
|
||||
<button
|
||||
nbButton
|
||||
fullWidth
|
||||
status="primary"
|
||||
size="large"
|
||||
[disabled]="submitted || !form.valid"
|
||||
[class.btn-pulse]="submitted"
|
||||
>
|
||||
Register
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- <section
|
||||
*ngIf="socialLinks && socialLinks.length > 0"
|
||||
class="links"
|
||||
aria-label="Social sign in"
|
||||
>
|
||||
or enter with:
|
||||
<div class="socials">
|
||||
<ng-container *ngFor="let socialLink of socialLinks">
|
||||
<a
|
||||
*ngIf="socialLink.link"
|
||||
[routerLink]="socialLink.link"
|
||||
[attr.target]="socialLink.target"
|
||||
[attr.class]="socialLink.icon"
|
||||
[class.with-icon]="socialLink.icon"
|
||||
>
|
||||
<nb-icon
|
||||
*ngIf="socialLink.icon; else title"
|
||||
[icon]="socialLink.icon"
|
||||
></nb-icon>
|
||||
<ng-template #title>{{ socialLink.title }}</ng-template>
|
||||
</a>
|
||||
<a
|
||||
*ngIf="socialLink.url"
|
||||
[attr.href]="socialLink.url"
|
||||
[attr.target]="socialLink.target"
|
||||
[attr.class]="socialLink.icon"
|
||||
[class.with-icon]="socialLink.icon"
|
||||
>
|
||||
<nb-icon
|
||||
*ngIf="socialLink.icon; else title"
|
||||
[icon]="socialLink.icon"
|
||||
></nb-icon>
|
||||
<ng-template #title>{{ socialLink.title }}</ng-template>
|
||||
</a>
|
||||
</ng-container>
|
||||
</div>
|
||||
</section> -->
|
||||
|
||||
<section class="another-action" aria-label="Sign in">
|
||||
Already have an account? <a class="text-link" routerLink="../login">Log in</a>
|
||||
</section>
|
||||
42
src/app/auth/register/register.component.ts
Normal file
42
src/app/auth/register/register.component.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { AngularFireAuth } from '@angular/fire/compat/auth';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-register',
|
||||
templateUrl: './register.component.html',
|
||||
})
|
||||
export class NgxRegisterComponent {
|
||||
user = {
|
||||
fullName: '',
|
||||
email: '',
|
||||
password: '',
|
||||
confirmPassword: ''
|
||||
};
|
||||
submitted = false;
|
||||
errors: string[] = [];
|
||||
|
||||
constructor(private fireAuth: AngularFireAuth, private router: Router) {}
|
||||
|
||||
async register() {
|
||||
this.submitted = true;
|
||||
try {
|
||||
if (this.user.password !== this.user.confirmPassword) {
|
||||
throw new Error('Password does not match the confirm password.');
|
||||
}
|
||||
|
||||
await this.fireAuth.createUserWithEmailAndPassword(this.user.email, this.user.password);
|
||||
// Registration successful, redirect to login page or dashboard
|
||||
this.router.navigate(['auth/login']);
|
||||
} catch (error) {
|
||||
// Handle registration errors
|
||||
console.error('Error registering user:', error);
|
||||
this.errors.push(error.message || 'An error occurred. Please try again later.');
|
||||
}
|
||||
this.submitted = false;
|
||||
}
|
||||
|
||||
getConfigValue(key: string): any {
|
||||
// Implement this method based on your configuration retrieval logic
|
||||
}
|
||||
}
|
||||
33
src/app/pages/forms/ai-resume/ai-resume.component.html
Normal file
33
src/app/pages/forms/ai-resume/ai-resume.component.html
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<nb-card>
|
||||
<nb-card-header>Create AI Resume</nb-card-header>
|
||||
<nb-card-body>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
<form><textarea
|
||||
class="form-control bg-light"
|
||||
id="professionalSummary"
|
||||
rows="15"
|
||||
|
||||
placeholder="Write Job Description"
|
||||
></textarea></form>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<button nbButton [status]=status outline [size]="size">
|
||||
AI Generate
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<div>--- placeholder for viewer ---</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
20
src/app/pages/forms/ai-resume/ai-resume.component.scss
Normal file
20
src/app/pages/forms/ai-resume/ai-resume.component.scss
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
@import '../../../@theme/styles/themes';
|
||||
|
||||
@include nb-install-component() {
|
||||
.buttons-row {
|
||||
margin: -0.5rem;
|
||||
}
|
||||
|
||||
button[nbButton] {
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
@include nb-ltr(margin-right, 0.5rem);
|
||||
@include nb-rtl(margin-left, 0.5rem);
|
||||
}
|
||||
|
||||
.actions-card {
|
||||
height: 8rem;
|
||||
}
|
||||
}
|
||||
13
src/app/pages/forms/ai-resume/ai-resume.component.ts
Normal file
13
src/app/pages/forms/ai-resume/ai-resume.component.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { NbComponentShape, NbComponentSize, NbComponentStatus } from '@nebular/theme';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-buttons',
|
||||
styleUrls: ['./ai-resume.component.scss'],
|
||||
templateUrl: './ai-resume.component.html',
|
||||
})
|
||||
export class AiResumeComponent {
|
||||
status: NbComponentStatus = 'primary' ;
|
||||
shapes: NbComponentShape[] = [ 'rectangle', 'semi-round', 'round' ];
|
||||
size: NbComponentSize = 'tiny';
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import { FormInputsComponent } from './form-inputs/form-inputs.component';
|
|||
import { FormLayoutsComponent } from './form-layouts/form-layouts.component';
|
||||
import { DatepickerComponent } from './datepicker/datepicker.component';
|
||||
import { ButtonsComponent } from './buttons/buttons.component';
|
||||
import { AiResumeComponent } from './ai-resume/ai-resume.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
|
|
@ -28,6 +29,10 @@ const routes: Routes = [
|
|||
path: 'buttons',
|
||||
component: ButtonsComponent,
|
||||
},
|
||||
{
|
||||
path: 'ai-resume',
|
||||
component: AiResumeComponent,
|
||||
},
|
||||
{
|
||||
path: 'datepicker',
|
||||
component: DatepickerComponent,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import { FormLayoutsComponent } from './form-layouts/form-layouts.component';
|
|||
import { DatepickerComponent } from './datepicker/datepicker.component';
|
||||
import { ButtonsComponent } from './buttons/buttons.component';
|
||||
import { FormsModule as ngFormsModule } from '@angular/forms';
|
||||
import { AiResumeComponent } from './ai-resume/ai-resume.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
|
@ -39,6 +40,7 @@ import { FormsModule as ngFormsModule } from '@angular/forms';
|
|||
declarations: [
|
||||
FormsComponent,
|
||||
ButtonsComponent,
|
||||
AiResumeComponent,
|
||||
FormInputsComponent,
|
||||
FormLayoutsComponent,
|
||||
DatepickerComponent,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { AccordionComponent } from './accordion/accordion.component';
|
|||
import { InfiniteListComponent } from './infinite-list/infinite-list.component';
|
||||
import { ListComponent } from './list/list.component';
|
||||
import { StepperComponent } from './stepper/stepper.component';
|
||||
import { ProfileComponent } from './stepper/profile/profile.component';
|
||||
|
||||
const routes: Routes = [{
|
||||
path: '',
|
||||
|
|
@ -16,6 +17,10 @@ const routes: Routes = [{
|
|||
path: 'stepper',
|
||||
component: StepperComponent,
|
||||
},
|
||||
{
|
||||
path: 'stepper/profile',
|
||||
component: ProfileComponent,
|
||||
},
|
||||
{
|
||||
path: 'list',
|
||||
component: ListComponent,
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import { NewsPostComponent } from './infinite-list/news-post/news-post.component
|
|||
import { NewsPostPlaceholderComponent } from './infinite-list/news-post-placeholder/news-post-placeholder.component';
|
||||
import { AccordionComponent } from './accordion/accordion.component';
|
||||
import { NewsService } from './news.service';
|
||||
import { ProfileComponent } from './stepper/profile/profile.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
|
@ -48,6 +49,7 @@ import { NewsService } from './news.service';
|
|||
InfiniteListComponent,
|
||||
NewsPostComponent,
|
||||
AccordionComponent,
|
||||
ProfileComponent,
|
||||
],
|
||||
providers: [
|
||||
NewsService,
|
||||
|
|
|
|||
781
src/app/pages/layout/stepper/profile/profile.component.html
Normal file
781
src/app/pages/layout/stepper/profile/profile.component.html
Normal file
|
|
@ -0,0 +1,781 @@
|
|||
<nb-card class="col-md-12 col-lg-12 col-xxxl-12">
|
||||
<nb-card-body>
|
||||
<nb-stepper orientation="horizontal">
|
||||
<nb-step [label]="labelOne">
|
||||
<ng-template #labelOne>Personal details</ng-template>
|
||||
|
||||
<form [formGroup]="personalDetails">
|
||||
<!-- Start of form -->
|
||||
<nb-card>
|
||||
<nb-card-header> <h3>Personal Details</h3></nb-card-header>
|
||||
<nb-card-body>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group text-left">
|
||||
<label
|
||||
for="username"
|
||||
class="text-muted font-weight-bold small"
|
||||
>Full Name</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control bg-light"
|
||||
id="username"
|
||||
placeholder="Full name"
|
||||
formControlName="username"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group text-left">
|
||||
<label
|
||||
for="phone"
|
||||
class="text-muted font-weight-bold small"
|
||||
>Phone Number</label
|
||||
>
|
||||
<input
|
||||
type="tel"
|
||||
class="form-control bg-light"
|
||||
id="phone"
|
||||
formControlName="phone"
|
||||
placeholder="Phone number"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-group text-left">
|
||||
<label
|
||||
for="linkedin"
|
||||
class="text-muted font-weight-bold small"
|
||||
>LinkedIn URL</label
|
||||
>
|
||||
<input
|
||||
type="url"
|
||||
class="form-control bg-light"
|
||||
id="linkedin"
|
||||
formControlName="linkedinLink"
|
||||
placeholder="LinkedIn URL"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group text-left">
|
||||
<label
|
||||
for="email"
|
||||
class="text-muted font-weight-bold small"
|
||||
>Email Address</label
|
||||
>
|
||||
<input
|
||||
type="email"
|
||||
class="form-control bg-light"
|
||||
id="email"
|
||||
formControlName="email"
|
||||
placeholder="Email address"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group text-left">
|
||||
<label
|
||||
for="website"
|
||||
class="text-muted font-weight-bold small"
|
||||
>Portfolio Link / Website</label
|
||||
>
|
||||
<input
|
||||
type="url"
|
||||
class="form-control bg-light"
|
||||
id="website"
|
||||
formControlName="portfolioLink"
|
||||
placeholder="Website link"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-12">
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="form-group text-left">
|
||||
<label
|
||||
for="address1"
|
||||
class="text-muted font-weight-bold small"
|
||||
>Street Address</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control bg-light"
|
||||
id="address1"
|
||||
formControlName="address1"
|
||||
placeholder="Address 1"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group text-left">
|
||||
<label
|
||||
for="city"
|
||||
class="text-muted font-weight-bold small"
|
||||
>City</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control bg-light"
|
||||
id="city"
|
||||
formControlName="city"
|
||||
placeholder="City"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-group text-left">
|
||||
<label for="zip" class="text-muted font-weight-bold small"
|
||||
>Zip</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control bg-light"
|
||||
id="zip"
|
||||
formControlName="zip"
|
||||
placeholder="Zip"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="form-group text-left">
|
||||
<label
|
||||
for="address2"
|
||||
class="text-muted font-weight-bold small"
|
||||
>Address 2</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control bg-light"
|
||||
id="address2"
|
||||
formControlName="address2"
|
||||
placeholder="Address 2"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group text-left">
|
||||
<label
|
||||
for="state"
|
||||
class="text-muted font-weight-bold small"
|
||||
>State</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control bg-light"
|
||||
id="state"
|
||||
formControlName="state"
|
||||
placeholder="State"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
<!-- end of form -->
|
||||
</form>
|
||||
|
||||
<!-- <button nbButton disabled nbStepperNext>prev</button> -->
|
||||
<button nbButton nbStepperNext>next</button>
|
||||
</nb-step>
|
||||
|
||||
<nb-step label="Experience">
|
||||
<form [formGroup]="experience">
|
||||
<!-- Start of form -->
|
||||
<nb-card>
|
||||
<nb-card-header><h3>Experience</h3></nb-card-header>
|
||||
<nb-card-body>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group text-left">
|
||||
<label
|
||||
for="role"
|
||||
class="text-muted font-weight-bold small"
|
||||
>What was your role at the company?</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control bg-light"
|
||||
id="role"
|
||||
formControlName="position"
|
||||
placeholder="Your role at the company"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-group text-left">
|
||||
<label
|
||||
for="location"
|
||||
class="text-muted font-weight-bold small"
|
||||
>Where was the company located?</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control bg-light"
|
||||
id="location"
|
||||
formControlName="location"
|
||||
placeholder="Company location"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-group text-left">
|
||||
<label
|
||||
for="duration"
|
||||
class="text-muted font-weight-bold small"
|
||||
>How long were you with the company?</label
|
||||
>
|
||||
<div class="input-group">
|
||||
<input
|
||||
nbInput
|
||||
type="date"
|
||||
class="form-control bg-light"
|
||||
id="duration"
|
||||
formControlName="startDate"
|
||||
placeholder="Start date"
|
||||
/>
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">-</span>
|
||||
</div>
|
||||
<input
|
||||
type="date"
|
||||
formControlName="endDate"
|
||||
class="form-control bg-light"
|
||||
placeholder="End date"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="form-group text-left">
|
||||
<label
|
||||
for="company"
|
||||
class="text-muted font-weight-bold small"
|
||||
>For which company did you work?</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control bg-light"
|
||||
id="company"
|
||||
formControlName="employer"
|
||||
placeholder="Company name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-group text-left">
|
||||
<label
|
||||
for="location"
|
||||
class="text-muted font-weight-bold small"
|
||||
>Company Website</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control bg-light"
|
||||
id="location"
|
||||
formControlName="companyLink"
|
||||
placeholder="Website of the company.."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-group text-left">
|
||||
<label
|
||||
for="location"
|
||||
class="text-muted font-weight-bold small"
|
||||
>Current Job</label
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-control bg-light"
|
||||
id="Current company"
|
||||
formControlName="currentJob"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div class="form-group text-left">
|
||||
<div><label
|
||||
for="responsibilities"
|
||||
class="text-muted font-weight-bold small"
|
||||
>What did you do at the company?</label
|
||||
> <button nbButton [status]=status outline [size]=size>
|
||||
AI Generate
|
||||
</button></div>
|
||||
<textarea
|
||||
class="form-control bg-light"
|
||||
id="responsibilities"
|
||||
rows="5"
|
||||
formControlName="description"
|
||||
placeholder="Enter your responsibilities"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
<!-- end of form -->
|
||||
</form>
|
||||
|
||||
<button nbButton nbStepperPrevious>prev</button>
|
||||
<button nbButton nbStepperNext>next</button>
|
||||
</nb-step>
|
||||
|
||||
<nb-step label="Project">
|
||||
<form [formGroup]="project">
|
||||
<!-- Start of form -->
|
||||
<nb-card>
|
||||
<nb-card-header><h3>Project</h3></nb-card-header>
|
||||
<nb-card-body>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group text-left">
|
||||
<label
|
||||
for="projectTitle"
|
||||
class="text-muted font-weight-bold small"
|
||||
>Give your project a title</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control bg-light"
|
||||
id="projectTitle"
|
||||
formControlName="name"
|
||||
placeholder="Project title"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-group text-left">
|
||||
<label
|
||||
for="projectDuration"
|
||||
class="text-muted font-weight-bold small"
|
||||
>When did you do your project?</label
|
||||
>
|
||||
<div class="input-group">
|
||||
<input
|
||||
type="date"
|
||||
class="form-control bg-light"
|
||||
id="projectDuration"
|
||||
formControlName="startDate"
|
||||
placeholder="Start date"
|
||||
/>
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">-</span>
|
||||
</div>
|
||||
<input
|
||||
type="date"
|
||||
formControlName="endDate"
|
||||
class="form-control bg-light"
|
||||
placeholder="End date"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="form-group text-left">
|
||||
<label
|
||||
for="organization"
|
||||
class="text-muted font-weight-bold small"
|
||||
>In which organization did you do your project?</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control bg-light"
|
||||
id="organization"
|
||||
formControlName="employer"
|
||||
placeholder="Organization name"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group text-left">
|
||||
<label
|
||||
for="projectURL"
|
||||
class="text-muted font-weight-bold small"
|
||||
>Project URL</label
|
||||
>
|
||||
<input
|
||||
type="url"
|
||||
class="form-control bg-light"
|
||||
id="projectURL"
|
||||
formControlName="link"
|
||||
placeholder="Project URL"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div class="form-group text-left">
|
||||
<div><label
|
||||
for="projectDescription"
|
||||
class="text-muted font-weight-bold small"
|
||||
>
|
||||
Now describe what you did</label
|
||||
> <button nbButton [status]=status outline [size]=size>
|
||||
AI Generate
|
||||
</button></div>
|
||||
<textarea
|
||||
class="form-control bg-light"
|
||||
id="projectDescription"
|
||||
rows="5"
|
||||
formControlName="description"
|
||||
placeholder="Describe your project"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
<!-- end of form -->
|
||||
</form>
|
||||
|
||||
<button nbButton nbStepperPrevious>prev</button>
|
||||
<button nbButton nbStepperNext>next</button>
|
||||
</nb-step>
|
||||
|
||||
<nb-step label="Education">
|
||||
<form [formGroup]="education">
|
||||
<!-- Start of form -->
|
||||
<nb-card>
|
||||
<nb-card-header><h3>Education</h3></nb-card-header>
|
||||
<nb-card-body>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group text-left">
|
||||
<label
|
||||
for="degree"
|
||||
class="text-muted font-weight-bold small"
|
||||
>What is your degree earned?</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control bg-light"
|
||||
id="degree"
|
||||
formControlName="degreeName"
|
||||
placeholder="Your degree and major"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6"></div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="form-group text-left">
|
||||
<label
|
||||
for="institution"
|
||||
class="text-muted font-weight-bold small"
|
||||
>Where did you earn your degree/qualification?</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control bg-light"
|
||||
id="institution"
|
||||
formControlName="school"
|
||||
placeholder="Institution name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-group text-left">
|
||||
<label
|
||||
for="degree"
|
||||
class="text-muted font-weight-bold small"
|
||||
>What is your major field of study?</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control bg-light"
|
||||
id="degree"
|
||||
formControlName="major"
|
||||
placeholder="Your major"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-group text-left">
|
||||
<label
|
||||
for="graduationYear"
|
||||
class="text-muted font-weight-bold small"
|
||||
>When did you earn your degree/qualification?</label
|
||||
>
|
||||
<div class="input-group">
|
||||
<input
|
||||
type="date"
|
||||
class="form-control bg-light"
|
||||
id="educationDuration"
|
||||
formControlName="startDate"
|
||||
placeholder="Start date"
|
||||
/>
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">-</span>
|
||||
</div>
|
||||
<input
|
||||
type="date"
|
||||
formControlName="endDate"
|
||||
class="form-control bg-light"
|
||||
placeholder="End date"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group text-left">
|
||||
<label
|
||||
for="institutionLocation"
|
||||
class="text-muted font-weight-bold small"
|
||||
>Where is the institution located?</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control bg-light"
|
||||
id="institutionLocation"
|
||||
formControlName="location"
|
||||
placeholder="Institution location"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group text-left">
|
||||
<label
|
||||
for="minor"
|
||||
class="text-muted font-weight-bold small"
|
||||
>Did you minor in anything?</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control bg-light"
|
||||
id="minor"
|
||||
formControlName="minor"
|
||||
placeholder="Minor subject"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group text-left">
|
||||
<label for="gpa" class="text-muted font-weight-bold small"
|
||||
>GPA (if applicable)</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control bg-light"
|
||||
id="gpa"
|
||||
formControlName="grade"
|
||||
placeholder="Your GPA"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-12">
|
||||
<div class="form-group text-left">
|
||||
<label
|
||||
for="additionalInfo"
|
||||
class="text-muted font-weight-bold small"
|
||||
>Open field for additional information</label
|
||||
>
|
||||
<textarea
|
||||
class="form-control bg-light"
|
||||
id="additionalInfo"
|
||||
rows="5"
|
||||
formControlName="description"
|
||||
placeholder="Additional information"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
<!-- end of form -->
|
||||
</form>
|
||||
|
||||
<button nbButton nbStepperPrevious>prev</button>
|
||||
<button nbButton nbStepperNext>next</button>
|
||||
</nb-step>
|
||||
|
||||
<nb-step label="Certifications">
|
||||
<form [formGroup]="certifications">
|
||||
<!-- Start of form -->
|
||||
<nb-card>
|
||||
<nb-card-header> <h3>Certifications</h3></nb-card-header>
|
||||
<nb-card-body>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group text-left">
|
||||
<label
|
||||
for="certificateName"
|
||||
class="text-muted font-weight-bold small"
|
||||
>What was the certificate name?</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control bg-light"
|
||||
id="certificateName"
|
||||
formControlName="name"
|
||||
placeholder="Certificate name"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group text-left">
|
||||
<label
|
||||
for="certificateYear"
|
||||
class="text-muted font-weight-bold small"
|
||||
>When did you get the certificate?</label
|
||||
>
|
||||
<div class="input-group">
|
||||
<input
|
||||
nbInput
|
||||
type="date"
|
||||
class="form-control bg-light"
|
||||
id="duration"
|
||||
formControlName="startDate"
|
||||
placeholder="Start date"
|
||||
/>
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">-</span>
|
||||
</div>
|
||||
<input
|
||||
type="date"
|
||||
formControlName="endDate"
|
||||
class="form-control bg-light"
|
||||
placeholder="End date"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="form-group text-left">
|
||||
<label
|
||||
for="certificateIssuer"
|
||||
class="text-muted font-weight-bold small"
|
||||
>Where did you get the certificate?</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control bg-light"
|
||||
id="certificateIssuer"
|
||||
formControlName="issuer"
|
||||
placeholder="Certificate issuer"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-12">
|
||||
<div class="form-group text-left">
|
||||
<label
|
||||
for="certificateRelevance"
|
||||
class="text-muted font-weight-bold small"
|
||||
>How is the certificate relevant?</label
|
||||
>
|
||||
<textarea
|
||||
class="form-control bg-light"
|
||||
id="certificateRelevance"
|
||||
rows="5"
|
||||
formControlName="description"
|
||||
placeholder="Certificate relevance"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
<!-- end of form -->
|
||||
</form>
|
||||
|
||||
<button nbButton nbStepperPrevious>prev</button>
|
||||
<button nbButton nbStepperNext>next</button>
|
||||
</nb-step>
|
||||
|
||||
<nb-step label="Skills">
|
||||
<form [formGroup]="skills">
|
||||
<!-- Start of form -->
|
||||
<nb-card>
|
||||
<nb-card-header><h3>Skills</h3></nb-card-header>
|
||||
<nb-card-body>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group text-left">
|
||||
<label
|
||||
for="skills"
|
||||
class="text-muted font-weight-bold small"
|
||||
>Enter the skills you possess</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
formControlName="name"
|
||||
class="form-control bg-light"
|
||||
placeholder="Write skill name..Eg; Python"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
<!-- end of form -->
|
||||
</form>
|
||||
|
||||
<button nbButton nbStepperPrevious>prev</button>
|
||||
<button nbButton nbStepperNext>next</button>
|
||||
</nb-step>
|
||||
|
||||
<nb-step [label]="labelFour">
|
||||
<ng-template #labelFour>Summary</ng-template>
|
||||
|
||||
<form [formGroup]="professionalSummary">
|
||||
<!-- Start of form -->
|
||||
<nb-card>
|
||||
<nb-card-header><h3>Summary</h3></nb-card-header>
|
||||
<nb-card-body>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="form-group text-left">
|
||||
<div><label
|
||||
for="professionalSummary"
|
||||
class="text-muted font-weight-bold small"
|
||||
>Write a professional summary</label
|
||||
><button nbButton [status]=status outline [size]=size>
|
||||
AI Generate
|
||||
</button></div>
|
||||
<textarea
|
||||
class="form-control bg-light"
|
||||
id="professionalSummary"
|
||||
rows="5"
|
||||
formControlName="professionalSummary"
|
||||
placeholder="Write your professional summary"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
<!-- end of form -->
|
||||
</form>
|
||||
|
||||
<button nbButton nbStepperPrevious>prev</button>
|
||||
<button nbButton nbStepperNext>next</button>
|
||||
<button nbButton (click)="submitForms()">Submit</button>
|
||||
</nb-step>
|
||||
|
||||
<nb-step [label]="labelFive">
|
||||
<ng-template #labelFive>Preview</ng-template>
|
||||
|
||||
<form [formGroup]="labelFive">
|
||||
<!-- Start of form -->
|
||||
<nb-card>
|
||||
<nb-card-header><h3>Preview</h3></nb-card-header>
|
||||
<nb-card-body>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="form-group text-left">
|
||||
<div>--- placeholder for viewer ---</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
<!-- end of form -->
|
||||
</form>
|
||||
|
||||
<button nbButton nbStepperPrevious>prev</button>
|
||||
|
||||
</nb-step>
|
||||
</nb-stepper>
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
:host ::ng-deep nb-stepper .step-content {
|
||||
text-align: center;
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
margin: 0.5rem;
|
||||
}
|
||||
}
|
||||
123
src/app/pages/layout/stepper/profile/profile.component.ts
Normal file
123
src/app/pages/layout/stepper/profile/profile.component.ts
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
import { NbComponentShape, NbComponentSize, NbComponentStatus } from '@nebular/theme';
|
||||
|
||||
import { UserAPI } from '../../../../service/api/user-api.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-profile',
|
||||
templateUrl: 'profile.component.html',
|
||||
styleUrls: ['profile.component.scss'],
|
||||
})
|
||||
export class ProfileComponent implements OnInit {
|
||||
|
||||
personalDetails: FormGroup;
|
||||
experience: FormGroup;
|
||||
project: FormGroup;
|
||||
education: FormGroup;
|
||||
certifications: FormGroup;
|
||||
skills: FormGroup;
|
||||
professionalSummary: FormGroup;
|
||||
userId: string
|
||||
|
||||
status: NbComponentStatus = 'primary' ;
|
||||
shapes: NbComponentShape[] = [ 'rectangle', 'semi-round', 'round' ];
|
||||
size: NbComponentSize = 'tiny';
|
||||
constructor(private fb: FormBuilder, private userAPI: UserAPI, private router: Router) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.personalDetails = this.fb.group({
|
||||
username: [''],
|
||||
email: [''],
|
||||
phone: [''],
|
||||
address1: [''],
|
||||
address2: [''],
|
||||
city: [''],
|
||||
state: [''],
|
||||
zip: [''],
|
||||
linkedinLink: [''],
|
||||
portfolioLink: [''],
|
||||
});
|
||||
|
||||
this.experience = this.fb.group({
|
||||
position: [''],
|
||||
employer: [''],
|
||||
location: [''],
|
||||
startDate: [''],
|
||||
endDate: [''],
|
||||
currentJob: [''],
|
||||
companyLink: [''],
|
||||
description: [''],
|
||||
});
|
||||
|
||||
this.project = this.fb.group({
|
||||
name: [''],
|
||||
link: [''],
|
||||
employer: [''],
|
||||
description: [''],
|
||||
startDate: [''],
|
||||
endDate: [''],
|
||||
});
|
||||
|
||||
this.education = this.fb.group({
|
||||
degreeName: [''],
|
||||
school: [''],
|
||||
location: [''],
|
||||
major: [''],
|
||||
minor: [''],
|
||||
startDate: [''],
|
||||
endDate: [''],
|
||||
grade: [''],
|
||||
description: [''],
|
||||
});
|
||||
|
||||
this.certifications = this.fb.group({
|
||||
name: [''],
|
||||
issuer: [''],
|
||||
startDate: [''],
|
||||
endDate: [''],
|
||||
description: [''],
|
||||
});
|
||||
|
||||
this.skills = this.fb.group({
|
||||
name: [''],
|
||||
});
|
||||
|
||||
this.professionalSummary = this.fb.group({
|
||||
professionalSummary: [''],
|
||||
});
|
||||
}
|
||||
|
||||
async submitForms() {
|
||||
try {
|
||||
// Combine professional summary with personal details
|
||||
const personalDetailsWithSummary = {
|
||||
...this.personalDetails.value,
|
||||
professionalSummary: this.professionalSummary.value.professionalSummary,
|
||||
password: 'password',
|
||||
role: 'USER'
|
||||
};
|
||||
|
||||
// Save personal details including professional summary
|
||||
const userDetailsResponse = await this.userAPI.saveUserDetails(personalDetailsWithSummary).toPromise();
|
||||
this.userId = userDetailsResponse.data.id;
|
||||
|
||||
// Save other details using the obtained user ID
|
||||
await this.userAPI.saveExperience(this.experience.value, this.userId).toPromise();
|
||||
await this.userAPI.saveEducation(this.education.value, this.userId).toPromise();
|
||||
await this.userAPI.saveProjects(this.project.value, this.userId).toPromise();
|
||||
await this.userAPI.saveSkills(this.skills.value, this.userId).toPromise();
|
||||
await this.userAPI.saveCertifications(this.certifications.value, this.userId).toPromise();
|
||||
|
||||
|
||||
console.log("userID: ", this.userId)
|
||||
|
||||
this.router.navigateByUrl(`/pages/layout/stepper/profile?userId=${this.userId}`);
|
||||
} catch (error) {
|
||||
console.error('Error occurred while saving user details:', error);
|
||||
// Handle error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -140,4 +140,4 @@
|
|||
</nb-card-body>
|
||||
</nb-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -40,4 +40,4 @@ export class StepperComponent implements OnInit {
|
|||
onThirdSubmit() {
|
||||
this.thirdForm.markAsDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,246 +1,42 @@
|
|||
import { NbMenuItem } from '@nebular/theme';
|
||||
import { NbMenuItem } from "@nebular/theme";
|
||||
|
||||
export const MENU_ITEMS: NbMenuItem[] = [
|
||||
{
|
||||
title: 'E-commerce',
|
||||
icon: 'shopping-cart-outline',
|
||||
link: '/pages/dashboard',
|
||||
title: "Home",
|
||||
icon: "home-outline",
|
||||
link: "/pages/dashboard",
|
||||
home: true,
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
title: 'IoT Dashboard',
|
||||
icon: 'home-outline',
|
||||
link: '/pages/iot-dashboard',
|
||||
},
|
||||
{
|
||||
title: 'FEATURES',
|
||||
title: "FEATURES",
|
||||
group: true,
|
||||
},
|
||||
{
|
||||
title: 'Layout',
|
||||
icon: 'layout-outline',
|
||||
title: "Create",
|
||||
icon: "layout-outline",
|
||||
children: [
|
||||
{
|
||||
title: 'Stepper',
|
||||
link: '/pages/layout/stepper',
|
||||
title: "Resume",
|
||||
link: "/pages/layout/stepper/profile",
|
||||
},
|
||||
{
|
||||
title: 'List',
|
||||
link: '/pages/layout/list',
|
||||
},
|
||||
{
|
||||
title: 'Infinite List',
|
||||
link: '/pages/layout/infinite-list',
|
||||
},
|
||||
{
|
||||
title: 'Accordion',
|
||||
link: '/pages/layout/accordion',
|
||||
},
|
||||
{
|
||||
title: 'Tabs',
|
||||
pathMatch: 'prefix',
|
||||
link: '/pages/layout/tabs',
|
||||
title: "Cover Letter",
|
||||
link: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Forms',
|
||||
icon: 'edit-2-outline',
|
||||
title: "AI",
|
||||
icon: "keypad-outline",
|
||||
children: [
|
||||
{
|
||||
title: 'Form Inputs',
|
||||
link: '/pages/forms/inputs',
|
||||
title: "AI Resume",
|
||||
link: "/pages/forms/ai-resume",
|
||||
},
|
||||
{
|
||||
title: 'Form Layouts',
|
||||
link: '/pages/forms/layouts',
|
||||
},
|
||||
{
|
||||
title: 'Buttons',
|
||||
link: '/pages/forms/buttons',
|
||||
},
|
||||
{
|
||||
title: 'Datepicker',
|
||||
link: '/pages/forms/datepicker',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'UI Features',
|
||||
icon: 'keypad-outline',
|
||||
link: '/pages/ui-features',
|
||||
children: [
|
||||
{
|
||||
title: 'Grid',
|
||||
link: '/pages/ui-features/grid',
|
||||
},
|
||||
{
|
||||
title: 'Icons',
|
||||
link: '/pages/ui-features/icons',
|
||||
},
|
||||
{
|
||||
title: 'Typography',
|
||||
link: '/pages/ui-features/typography',
|
||||
},
|
||||
{
|
||||
title: 'Animated Searches',
|
||||
link: '/pages/ui-features/search-fields',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Modal & Overlays',
|
||||
icon: 'browser-outline',
|
||||
children: [
|
||||
{
|
||||
title: 'Dialog',
|
||||
link: '/pages/modal-overlays/dialog',
|
||||
},
|
||||
{
|
||||
title: 'Window',
|
||||
link: '/pages/modal-overlays/window',
|
||||
},
|
||||
{
|
||||
title: 'Popover',
|
||||
link: '/pages/modal-overlays/popover',
|
||||
},
|
||||
{
|
||||
title: 'Toastr',
|
||||
link: '/pages/modal-overlays/toastr',
|
||||
},
|
||||
{
|
||||
title: 'Tooltip',
|
||||
link: '/pages/modal-overlays/tooltip',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Extra Components',
|
||||
icon: 'message-circle-outline',
|
||||
children: [
|
||||
{
|
||||
title: 'Calendar',
|
||||
link: '/pages/extra-components/calendar',
|
||||
},
|
||||
{
|
||||
title: 'Progress Bar',
|
||||
link: '/pages/extra-components/progress-bar',
|
||||
},
|
||||
{
|
||||
title: 'Spinner',
|
||||
link: '/pages/extra-components/spinner',
|
||||
},
|
||||
{
|
||||
title: 'Alert',
|
||||
link: '/pages/extra-components/alert',
|
||||
},
|
||||
{
|
||||
title: 'Calendar Kit',
|
||||
link: '/pages/extra-components/calendar-kit',
|
||||
},
|
||||
{
|
||||
title: 'Chat',
|
||||
link: '/pages/extra-components/chat',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Maps',
|
||||
icon: 'map-outline',
|
||||
children: [
|
||||
{
|
||||
title: 'Google Maps',
|
||||
link: '/pages/maps/gmaps',
|
||||
},
|
||||
{
|
||||
title: 'Leaflet Maps',
|
||||
link: '/pages/maps/leaflet',
|
||||
},
|
||||
{
|
||||
title: 'Bubble Maps',
|
||||
link: '/pages/maps/bubble',
|
||||
},
|
||||
{
|
||||
title: 'Search Maps',
|
||||
link: '/pages/maps/searchmap',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Charts',
|
||||
icon: 'pie-chart-outline',
|
||||
children: [
|
||||
{
|
||||
title: 'Echarts',
|
||||
link: '/pages/charts/echarts',
|
||||
},
|
||||
{
|
||||
title: 'Charts.js',
|
||||
link: '/pages/charts/chartjs',
|
||||
},
|
||||
{
|
||||
title: 'D3',
|
||||
link: '/pages/charts/d3',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Editors',
|
||||
icon: 'text-outline',
|
||||
children: [
|
||||
{
|
||||
title: 'TinyMCE',
|
||||
link: '/pages/editors/tinymce',
|
||||
},
|
||||
{
|
||||
title: 'CKEditor',
|
||||
link: '/pages/editors/ckeditor',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Tables & Data',
|
||||
icon: 'grid-outline',
|
||||
children: [
|
||||
{
|
||||
title: 'Smart Table',
|
||||
link: '/pages/tables/smart-table',
|
||||
},
|
||||
{
|
||||
title: 'Tree Grid',
|
||||
link: '/pages/tables/tree-grid',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Miscellaneous',
|
||||
icon: 'shuffle-2-outline',
|
||||
children: [
|
||||
{
|
||||
title: '404',
|
||||
link: '/pages/miscellaneous/404',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Auth',
|
||||
icon: 'lock-outline',
|
||||
children: [
|
||||
{
|
||||
title: 'Login',
|
||||
link: '/auth/login',
|
||||
},
|
||||
{
|
||||
title: 'Register',
|
||||
link: '/auth/register',
|
||||
},
|
||||
{
|
||||
title: 'Request Password',
|
||||
link: '/auth/request-password',
|
||||
},
|
||||
{
|
||||
title: 'Reset Password',
|
||||
link: '/auth/reset-password',
|
||||
title: "AI Cover Letter",
|
||||
link: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
|||
53
src/app/service/api/user-api.service.ts
Normal file
53
src/app/service/api/user-api.service.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class UserAPI {
|
||||
|
||||
private baseUrl = 'http://localhost:8080/api';
|
||||
private token: string;
|
||||
|
||||
constructor(private http: HttpClient) {
|
||||
this.token = localStorage.getItem('accessToken');
|
||||
}
|
||||
|
||||
// Saving user details
|
||||
saveUserDetails(data: any): Observable<any> {
|
||||
const headers = new HttpHeaders().set('Authorization', `Bearer ${this.token}`);
|
||||
return this.http.post(`${this.baseUrl}/users`, data, { headers });
|
||||
}
|
||||
|
||||
// Get single user
|
||||
getUserDetails(userId: string): Observable<any> {
|
||||
const headers = new HttpHeaders().set('Authorization', `Bearer ${this.token}`);
|
||||
return this.http.get<any>(`${this.baseUrl}/users/${userId}`, { headers });
|
||||
}
|
||||
|
||||
saveExperience(data: any, userId: string): Observable<any> {
|
||||
const headers = new HttpHeaders().set('Authorization', `Bearer ${this.token}`);
|
||||
return this.http.post(`${this.baseUrl}/experiences?userId=${userId}`, data, { headers });
|
||||
}
|
||||
|
||||
saveEducation(data: any, userId: string): Observable<any> {
|
||||
const headers = new HttpHeaders().set('Authorization', `Bearer ${this.token}`);
|
||||
return this.http.post(`${this.baseUrl}/education?userId=${userId}`, data, { headers });
|
||||
}
|
||||
|
||||
saveProjects(data: any, userId: string): Observable<any> {
|
||||
const headers = new HttpHeaders().set('Authorization', `Bearer ${this.token}`);
|
||||
return this.http.post(`${this.baseUrl}/projects?userId=${userId}`, data, { headers });
|
||||
}
|
||||
|
||||
saveSkills(data: any, userId: string): Observable<any> {
|
||||
const headers = new HttpHeaders().set('Authorization', `Bearer ${this.token}`);
|
||||
return this.http.post(`${this.baseUrl}/skills?userId=${userId}`, data, { headers });
|
||||
}
|
||||
|
||||
saveCertifications(data: any, userId: string): Observable<any> {
|
||||
const headers = new HttpHeaders().set('Authorization', `Bearer ${this.token}`);
|
||||
return this.http.post(`${this.baseUrl}/certifications?userId=${userId}`, data, { headers });
|
||||
}
|
||||
}
|
||||
29
src/app/service/auth-guard.service.ts
Normal file
29
src/app/service/auth-guard.service.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { CanActivate, Router } from '@angular/router';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AuthGuard implements CanActivate {
|
||||
|
||||
constructor(private router: Router) {}
|
||||
|
||||
canActivate(): boolean {
|
||||
const accessToken = localStorage.getItem('accessToken');
|
||||
const accessTokenExpiresIn = localStorage.getItem('accessTokenExpiresIn');
|
||||
|
||||
// Check if access token and expiration time exist
|
||||
if (accessToken && accessTokenExpiresIn) {
|
||||
// Check if the access token is not expired
|
||||
const currentTime = new Date().getTime();
|
||||
const expiresIn = +accessTokenExpiresIn;
|
||||
if (currentTime < expiresIn) {
|
||||
return true; // Allow access to the route
|
||||
}
|
||||
}
|
||||
|
||||
// If access token is not found or expired, navigate to login page
|
||||
this.router.navigate(['/auth/login']);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
27
src/app/service/auth.service.ts
Normal file
27
src/app/service/auth.service.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { AngularFireAuth } from '@angular/fire/compat/auth';
|
||||
import { Router } from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AuthService {
|
||||
user$: Observable<firebase.default.User | null>;
|
||||
|
||||
constructor(private fireAuth: AngularFireAuth, private router: Router) {
|
||||
this.user$ = this.fireAuth.authState;
|
||||
}
|
||||
|
||||
// logout method
|
||||
async logout(): Promise<void> {
|
||||
try {
|
||||
await this.fireAuth.signOut();
|
||||
localStorage.removeItem('accessToken');
|
||||
localStorage.removeItem('accessTokenExpiresIn');
|
||||
this.router.navigate(['auth/login'], { queryParams: { logout: true } });
|
||||
} catch (error: any) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
6
src/app/utils/response-wrapper.ts
Normal file
6
src/app/utils/response-wrapper.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export interface ResponseWrapper<T> {
|
||||
success: boolean;
|
||||
message: string;
|
||||
data: T;
|
||||
error?: any;
|
||||
}
|
||||
BIN
src/assets/images/preview.png
Normal file
BIN
src/assets/images/preview.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 79 KiB |
BIN
src/assets/images/preview2.png
Normal file
BIN
src/assets/images/preview2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
BIN
src/assets/images/resume_logo.jpg
Normal file
BIN
src/assets/images/resume_logo.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
src/assets/images/thread.png
Normal file
BIN
src/assets/images/thread.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.9 KiB |
|
|
@ -10,4 +10,12 @@
|
|||
|
||||
export const environment = {
|
||||
production: false,
|
||||
firebase: {
|
||||
apiKey: "AIzaSyCPFCMSkeRtJJhiu_yWLIggkRpFL_OM88M",
|
||||
authDomain: "test-app-133d0.firebaseapp.com",
|
||||
projectId: "test-app-133d0",
|
||||
storageBucket: "test-app-133d0.appspot.com",
|
||||
messagingSenderId: "166874967658",
|
||||
appId: "1:166874967658:web:53ef3ec2bb8456d9e2a413"
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>ngx-admin Demo Application</title>
|
||||
<title>Resume Tailor Application</title>
|
||||
|
||||
<base href="/">
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue