This commit is contained in:
Anish Gurung 2024-03-24 04:23:43 +00:00 committed by GitHub
commit 8b7150b6b2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
41 changed files with 33037 additions and 676 deletions

2
.gitignore vendored
View file

@ -40,3 +40,5 @@ testem.log
# System Files
.DS_Store
Thumbs.db
/.angular

View file

@ -187,5 +187,8 @@
"@angular-eslint/schematics:library": {
"setParserOptionsProject": true
}
},
"cli": {
"analytics": false
}
}

31529
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -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",

View file

@ -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' },

View file

@ -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>

View file

@ -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"
<nb-user
[nbContextMenu]="userMenu"
[onlyPicture]="userPictureOnly"
[name]="user?.name"
[picture]="user?.picture">
[picture]="user?.picture"
>
</nb-user>
</nb-action>
</nb-actions>

View file

@ -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) {

View file

@ -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();
}
}

View file

@ -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,
loadChildren: () => import('./auth/auth.module').then(m => m.NgxAuthModule)
},
{
path: 'login',
component: NbLoginComponent,
},
{
path: 'register',
component: NbRegisterComponent,
},
{
path: 'logout',
component: NbLogoutComponent,
},
{
path: 'request-password',
component: NbRequestPasswordComponent,
},
{
path: 'reset-password',
component: NbResetPasswordComponent,
},
],
},
{ path: '', redirectTo: 'pages', pathMatch: 'full' },
{ path: '**', redirectTo: 'pages' },
];

View file

@ -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],
})

View 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,
// ),
// };
}

View 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 {
}

View 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 {
}

View 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>

View 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
}
}

View 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>

View 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
}
}

View 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>

View 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;
}
}

View 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';
}

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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,

View 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>

View file

@ -0,0 +1,8 @@
:host ::ng-deep nb-stepper .step-content {
text-align: center;
button {
cursor: pointer;
margin: 0.5rem;
}
}

View 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
}
}
}

View file

@ -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: "",
},
],
},

View 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 });
}
}

View 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;
}
}

View 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;
}
}
}

View file

@ -0,0 +1,6 @@
export interface ResponseWrapper<T> {
success: boolean;
message: string;
data: T;
error?: any;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

View file

@ -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"
}
};

View file

@ -2,7 +2,7 @@
<html>
<head>
<meta charset="utf-8">
<title>ngx-admin Demo Application</title>
<title>Resume Tailor Application</title>
<base href="/">