32 Commits

Author SHA1 Message Date
rajukottedi 797163d1ba chore: update Angular dependencies to version 20.2.0 and refactor HTTP interceptors to use new functional API
- Updated Angular dependencies in package.json to version 20.2.0, including Angular Material and CDK.
- Refactored AuthInterceptor and LoadingInterceptor to use the new HttpInterceptorFn functional API.
- Modified the index file for interceptors to export the new functional interceptors.
- Enhanced the styling of the project detail popup with a dark theme and encapsulation settings.
- Updated main.ts to use the new interceptor setup with provideHttpClient.
- Added theming support for Angular Material in styles.scss, including light and dark themes.
2026-03-14 22:54:01 +05:30
rajukottedi b4c5fddf0d Merge remote-tracking branch 'origin/task/upgrade-to-angular20' into task-project-view-component-changes 2026-03-14 17:45:41 +05:30
rajukottedi 67f668fdd8 feat: update dependencies and add Angular Material components
- Added @angular/cdk and @angular/material to package.json.
- Updated routing titles for better clarity in app-routing.module.ts.
- Integrated MatDialogModule in app.module.ts for dialog functionality.
- Enhanced project model with additional fields: challenges, lessonsLearned, impact, startDate, endDate, status, and resumeId.
- Improved projects.component.html for better structure and readability.
- Implemented openViewProject method in projects.component.ts to handle project detail dialog.
- Added Roboto font and Material Icons in index.html for UI consistency.
- Updated global styles in styles.scss for better layout and font settings.
- Created project-detail-popup component with HTML, SCSS, and TypeScript files for displaying project details in a dialog.
2026-03-14 17:36:16 +05:30
Bangara Raju 3cb47c7c1a chore: upgrade Angular and related dependencies to version 20.0.0
feat: refactor AboutComponent to be standalone and include CommonModule
feat: refactor BlogComponent to be standalone and include CommonModule
feat: refactor ContactSidebarComponent to be standalone and include CommonModule
feat: refactor ContactComponent to be standalone and include CommonModule and FormsModule
feat: refactor NavbarComponent to be standalone and include CommonModule and RouterModule
feat: refactor ProjectsComponent to be standalone and include CommonModule
feat: refactor ResumeComponent to be standalone and include CommonModule
feat: refactor AppComponent to be standalone and include necessary components
feat: remove AppModule in favor of standalone components
fix: update tests to include HttpClientTestingModule where necessary
fix: update routing module to export appRoutes without NgModule
chore: add environment configuration for production
2026-03-14 09:38:15 +05:30
rajukottedi 5351a2ba58 project tile changes 2024-05-19 12:23:35 +05:30
rajukottedi 0bb9097513 code refactoring 2024-05-19 11:37:27 +05:30
rajukottedi d7cad0d5c7 style Changes 2024-05-09 20:01:15 +05:30
rajukottedi c051f4f60e code refactoring 2024-05-09 13:47:11 +05:30
rajukottedi 14cdd20a59 code refactoring: style changes 2024-05-09 13:41:02 +05:30
rajukottedi 395f52060e code refactoring 2024-05-09 01:04:41 +05:30
rajukottedi fe79623464 code refactoring 2024-05-06 23:20:51 +05:30
rajukottedi fd43f9c1e6 contact form validations on submit 2024-05-05 15:54:26 +05:30
rajukottedi fc5de6587b added fontawesome 6.5.2 files 2024-05-04 00:47:39 +05:30
rajukottedi 3212a81a58 changed font awesome package link 2024-05-04 00:19:37 +05:30
rajukottedi e0fc82fa28 code refactoring 2024-05-02 17:57:43 +05:30
rajukottedi 8afb553158 router not match fix 2024-05-02 17:41:14 +05:30
rajukottedi d1604bfd88 app route issue fix when matching route doesn't exist 2024-05-02 17:22:34 +05:30
rajukottedi 11e62230d9 code refactoring 2024-05-02 17:06:09 +05:30
rajukottedi 3e80780fce sidebar candidate info not loading even after service is up 2024-05-02 00:27:52 +05:30
rajukottedi 26e7bd5d44 contact form implementation 2024-05-01 23:25:49 +05:30
rajukottedi 62ead2b2fe added api versions 2024-04-30 14:21:08 +05:30
rajukottedi 492c4704aa added embed map in contact page 2024-04-29 00:18:43 +05:30
rajukottedi 549444dd59 code refactoring 2024-04-28 23:31:27 +05:30
rajukottedi f781ad43f4 optimized api calls by moving api call to individual components and improved performance 2024-04-28 23:12:40 +05:30
rajukottedi edf9865efc code refactoring 2024-04-28 17:07:53 +05:30
rajukottedi 1ca5e82426 code refactoring 2024-04-28 15:32:25 +05:30
rajukottedi c1250b7649 Adder Global Loading spiner for api calls 2024-04-28 15:32:15 +05:30
rajukottedi 1d53333667 added AuthInterceptor for sending api key in the header on every request to api 2024-04-28 14:03:41 +05:30
rajukottedi ad3df7c6b5 UI data binding changes 2024-04-28 13:31:31 +05:30
rajukottedi 5c04faad2a changes: api integration,
components, styles, fontawesome icons added
2024-04-26 02:32:27 +05:30
rajukottedi edd2798581 created components, routing and applied styles 2024-04-15 15:26:22 +05:30
rajukottedi 00d7c75564 updated angular cli 2024-04-09 17:28:24 +05:30
104 changed files with 13209 additions and 5958 deletions
+23 -5
View File
@@ -30,6 +30,7 @@
"src/assets"
],
"styles": [
"@angular/material/prebuilt-themes/indigo-pink.css",
"src/styles.scss"
],
"scripts": []
@@ -48,7 +49,13 @@
"maximumError": "4kb"
}
],
"outputHashing": "all"
"outputHashing": "all",
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.production.ts"
}
]
},
"development": {
"buildOptimizer": false,
@@ -56,7 +63,13 @@
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
"namedChunks": true,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.development.ts"
}
]
}
},
"defaultConfiguration": "production"
@@ -65,10 +78,11 @@
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "my-portfolio:build:production"
"buildTarget": "my-portfolio:build:production"
},
"development": {
"browserTarget": "my-portfolio:build:development"
"buildTarget": "my-portfolio:build:development",
"proxyConfig": "src/proxy.conf.json"
}
},
"defaultConfiguration": "development"
@@ -76,7 +90,7 @@
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "my-portfolio:build"
"buildTarget": "my-portfolio:build"
}
},
"test": {
@@ -93,6 +107,7 @@
"src/assets"
],
"styles": [
"@angular/material/prebuilt-themes/indigo-pink.css",
"src/styles.scss"
],
"scripts": []
@@ -100,5 +115,8 @@
}
}
}
},
"cli": {
"analytics": false
}
}
+9097 -5415
View File
File diff suppressed because it is too large Load Diff
+16 -14
View File
@@ -10,22 +10,24 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^16.1.0",
"@angular/common": "^16.1.0",
"@angular/compiler": "^16.1.0",
"@angular/core": "^16.1.0",
"@angular/forms": "^16.1.0",
"@angular/platform-browser": "^16.1.0",
"@angular/platform-browser-dynamic": "^16.1.0",
"@angular/router": "^16.1.0",
"@angular/animations": "^20.2.0",
"@angular/cdk": "^20.2.0",
"@angular/common": "^20.2.0",
"@angular/compiler": "^20.2.0",
"@angular/core": "^20.2.0",
"@angular/forms": "^20.2.0",
"@angular/material": "^20.2.0",
"@angular/platform-browser": "^20.2.0",
"@angular/platform-browser-dynamic": "^20.2.0",
"@angular/router": "^20.2.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.13.0"
"zone.js": "~0.15.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "^16.1.4",
"@angular/cli": "~16.1.4",
"@angular/compiler-cli": "^16.1.0",
"@angular-devkit/build-angular": "^20.2.0",
"@angular/cli": "~20.2.0",
"@angular/compiler-cli": "^20.2.0",
"@types/jasmine": "~4.3.0",
"jasmine-core": "~4.6.0",
"karma": "~6.4.0",
@@ -33,6 +35,6 @@
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"typescript": "~5.1.3"
"typescript": "~5.8.0"
}
}
}
+39
View File
@@ -0,0 +1,39 @@
<article class="about active" data-page="about">
<header>
<h2 class="h2 article-title">About me</h2>
</header>
<section class="about-text">
<pre class="text-style">
{{model.about}}
</pre>
</section>
<!--
- service
-->
<section class="service">
<h3 class="h3 service-title">Interests</h3>
<ul class="service-list">
<li class="service-item" *ngFor="let hobby of model.hobbies">
<div class="service-icon-box">
<i class="fa-regular fa-2x " [ngClass]="hobby.icon"></i>
</div>
<div class="service-content-box">
<h4 class="h4 service-item-title">{{hobby.name}}</h4>
<p class="service-item-text">
{{hobby.description}}
</p>
</div>
</li>
</ul>
</section>
</article>
+4
View File
@@ -0,0 +1,4 @@
.text-style {
white-space: pre-line;
font-family: var(--ff-poppins);
}
+24
View File
@@ -0,0 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { AboutComponent } from './about.component';
describe('AboutComponent', () => {
let component: AboutComponent;
let fixture: ComponentFixture<AboutComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AboutComponent, HttpClientTestingModule]
})
.compileComponents();
fixture = TestBed.createComponent(AboutComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
+29
View File
@@ -0,0 +1,29 @@
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CvService } from '../services/cv.service';
import { IAbout } from './about.model';
import { BaseComponent } from '../base/base.component';
@Component({
selector: 'app-about',
templateUrl: './about.component.html',
styleUrl: './about.component.scss',
standalone: true,
imports: [CommonModule]
})
export class AboutComponent extends BaseComponent<IAbout> implements OnInit {
constructor(svc: CvService){
super(svc);
}
ngOnInit(): void {
this.getAbout();
}
getAbout(): void{
this.svc.getHobbies(this.candidateId).subscribe((response: IAbout) => {
this.svc.about = this.svc.about ?? response;
this.assignData(response);
});
}
}
+6
View File
@@ -0,0 +1,6 @@
import { IHobby } from "../models/hobby.model";
export interface IAbout{
about: string;
hobbies: IHobby[];
}
+15
View File
@@ -0,0 +1,15 @@
import { Routes } from '@angular/router';
import { AboutComponent } from './about/about.component';
import { ResumeComponent } from './resume/resume.component';
import { ProjectsComponent } from './projects/projects.component';
import { BlogComponent } from './blog/blog.component';
import { ContactComponent } from './contact/contact.component';
export const appRoutes: Routes = [
{ path: '', component: AboutComponent, title: "Bangara Raju Kottedi" },
{ path: 'resume', component: ResumeComponent, title: "Bangara Raju Kottedi - Resume" },
{ path: 'projects', component: ProjectsComponent, title: "Bangara Raju Kottedi - Projects" },
{ path: 'weblog', component: BlogComponent, title: "Bangara Raju Kottedi - Posts" },
{ path: 'contact', component: ContactComponent, title: "Bangara Raju Kottedi - Contact" },
{ path: '**', redirectTo: '/'}
];
+7 -482
View File
@@ -1,483 +1,8 @@
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
<!-- * * * * * * * * * * * The content below * * * * * * * * * * * -->
<!-- * * * * * * * * * * is only a placeholder * * * * * * * * * * -->
<!-- * * * * * * * * * * and can be replaced. * * * * * * * * * * * -->
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
<!-- * * * * * * * * * Delete the template below * * * * * * * * * * -->
<!-- * * * * * * * to get started with your project! * * * * * * * * -->
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
<style>
:host {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-size: 14px;
color: #333;
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin: 8px 0;
}
p {
margin: 0;
}
.spacer {
flex: 1;
}
.toolbar {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 60px;
display: flex;
align-items: center;
background-color: #1976d2;
color: white;
font-weight: 600;
}
.toolbar img {
margin: 0 16px;
}
.toolbar #twitter-logo {
height: 40px;
margin: 0 8px;
}
.toolbar #youtube-logo {
height: 40px;
margin: 0 16px;
}
.toolbar #twitter-logo:hover,
.toolbar #youtube-logo:hover {
opacity: 0.8;
}
.content {
display: flex;
margin: 82px auto 32px;
padding: 0 16px;
max-width: 960px;
flex-direction: column;
align-items: center;
}
svg.material-icons {
height: 24px;
width: auto;
}
svg.material-icons:not(:last-child) {
margin-right: 8px;
}
.card svg.material-icons path {
fill: #888;
}
.card-container {
display: flex;
flex-wrap: wrap;
justify-content: center;
margin-top: 16px;
}
.card {
all: unset;
border-radius: 4px;
border: 1px solid #eee;
background-color: #fafafa;
height: 40px;
width: 200px;
margin: 0 8px 16px;
padding: 16px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
transition: all 0.2s ease-in-out;
line-height: 24px;
}
.card-container .card:not(:last-child) {
margin-right: 0;
}
.card.card-small {
height: 16px;
width: 168px;
}
.card-container .card:not(.highlight-card) {
cursor: pointer;
}
.card-container .card:not(.highlight-card):hover {
transform: translateY(-3px);
box-shadow: 0 4px 17px rgba(0, 0, 0, 0.35);
}
.card-container .card:not(.highlight-card):hover .material-icons path {
fill: rgb(105, 103, 103);
}
.card.highlight-card {
background-color: #1976d2;
color: white;
font-weight: 600;
border: none;
width: auto;
min-width: 30%;
position: relative;
}
.card.card.highlight-card span {
margin-left: 60px;
}
svg#rocket {
width: 80px;
position: absolute;
left: -10px;
top: -24px;
}
svg#rocket-smoke {
height: calc(100vh - 95px);
position: absolute;
top: 10px;
right: 180px;
z-index: -10;
}
a,
a:visited,
a:hover {
color: #1976d2;
text-decoration: none;
}
a:hover {
color: #125699;
}
.terminal {
position: relative;
width: 80%;
max-width: 600px;
border-radius: 6px;
padding-top: 45px;
margin-top: 8px;
overflow: hidden;
background-color: rgb(15, 15, 16);
}
.terminal::before {
content: "\2022 \2022 \2022";
position: absolute;
top: 0;
left: 0;
height: 4px;
background: rgb(58, 58, 58);
color: #c2c3c4;
width: 100%;
font-size: 2rem;
line-height: 0;
padding: 14px 0;
text-indent: 4px;
}
.terminal pre {
font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;
color: white;
padding: 0 1rem 1rem;
margin: 0;
}
.circle-link {
height: 40px;
width: 40px;
border-radius: 40px;
margin: 8px;
background-color: white;
border: 1px solid #eeeeee;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
transition: 1s ease-out;
}
.circle-link:hover {
transform: translateY(-0.25rem);
box-shadow: 0px 3px 15px rgba(0, 0, 0, 0.2);
}
footer {
margin-top: 8px;
display: flex;
align-items: center;
line-height: 20px;
}
footer a {
display: flex;
align-items: center;
}
.github-star-badge {
color: #24292e;
display: flex;
align-items: center;
font-size: 12px;
padding: 3px 10px;
border: 1px solid rgba(27,31,35,.2);
border-radius: 3px;
background-image: linear-gradient(-180deg,#fafbfc,#eff3f6 90%);
margin-left: 4px;
font-weight: 600;
}
.github-star-badge:hover {
background-image: linear-gradient(-180deg,#f0f3f6,#e6ebf1 90%);
border-color: rgba(27,31,35,.35);
background-position: -.5em;
}
.github-star-badge .material-icons {
height: 16px;
width: 16px;
margin-right: 4px;
}
svg#clouds {
position: fixed;
bottom: -160px;
left: -230px;
z-index: -10;
width: 1920px;
}
/* Responsive Styles */
@media screen and (max-width: 767px) {
.card-container > *:not(.circle-link) ,
.terminal {
width: 100%;
}
.card:not(.highlight-card) {
height: 16px;
margin: 8px 0;
}
.card.highlight-card span {
margin-left: 72px;
}
svg#rocket-smoke {
right: 120px;
transform: rotate(-5deg);
}
}
@media screen and (max-width: 575px) {
svg#rocket-smoke {
display: none;
visibility: hidden;
}
}
</style>
<!-- Toolbar -->
<div class="toolbar" role="banner">
<img
width="40"
alt="Angular Logo"
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg=="
/>
<span>Welcome</span>
<div class="spacer"></div>
<a aria-label="Angular on twitter" target="_blank" rel="noopener" href="https://twitter.com/angular" title="Twitter">
<svg id="twitter-logo" height="24" data-name="Logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400">
<rect width="400" height="400" fill="none"/>
<path d="M153.62,301.59c94.34,0,145.94-78.16,145.94-145.94,0-2.22,0-4.43-.15-6.63A104.36,104.36,0,0,0,325,122.47a102.38,102.38,0,0,1-29.46,8.07,51.47,51.47,0,0,0,22.55-28.37,102.79,102.79,0,0,1-32.57,12.45,51.34,51.34,0,0,0-87.41,46.78A145.62,145.62,0,0,1,92.4,107.81a51.33,51.33,0,0,0,15.88,68.47A50.91,50.91,0,0,1,85,169.86c0,.21,0,.43,0,.65a51.31,51.31,0,0,0,41.15,50.28,51.21,51.21,0,0,1-23.16.88,51.35,51.35,0,0,0,47.92,35.62,102.92,102.92,0,0,1-63.7,22A104.41,104.41,0,0,1,75,278.55a145.21,145.21,0,0,0,78.62,23" fill="#fff"/>
</svg>
</a>
<a aria-label="Angular on YouTube" target="_blank" rel="noopener" href="https://youtube.com/angular" title="YouTube">
<svg id="youtube-logo" height="24" width="24" data-name="Logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#fff">
<path d="M0 0h24v24H0V0z" fill="none"/>
<path d="M21.58 7.19c-.23-.86-.91-1.54-1.77-1.77C18.25 5 12 5 12 5s-6.25 0-7.81.42c-.86.23-1.54.91-1.77 1.77C2 8.75 2 12 2 12s0 3.25.42 4.81c.23.86.91 1.54 1.77 1.77C5.75 19 12 19 12 19s6.25 0 7.81-.42c.86-.23 1.54-.91 1.77-1.77C22 15.25 22 12 22 12s0-3.25-.42-4.81zM10 15V9l5.2 3-5.2 3z"/>
</svg>
</a>
</div>
<div class="content" role="main">
<!-- Highlight Card -->
<div class="card highlight-card card-small">
<svg id="rocket" xmlns="http://www.w3.org/2000/svg" width="101.678" height="101.678" viewBox="0 0 101.678 101.678">
<title>Rocket Ship</title>
<g id="Group_83" data-name="Group 83" transform="translate(-141 -696)">
<circle id="Ellipse_8" data-name="Ellipse 8" cx="50.839" cy="50.839" r="50.839" transform="translate(141 696)" fill="#dd0031"/>
<g id="Group_47" data-name="Group 47" transform="translate(165.185 720.185)">
<path id="Path_33" data-name="Path 33" d="M3.4,42.615a3.084,3.084,0,0,0,3.553,3.553,21.419,21.419,0,0,0,12.215-6.107L9.511,30.4A21.419,21.419,0,0,0,3.4,42.615Z" transform="translate(0.371 3.363)" fill="#fff"/>
<path id="Path_34" data-name="Path 34" d="M53.3,3.221A3.09,3.09,0,0,0,50.081,0,48.227,48.227,0,0,0,18.322,13.437c-6-1.666-14.991-1.221-18.322,7.218A33.892,33.892,0,0,1,9.439,25.1l-.333.666a3.013,3.013,0,0,0,.555,3.553L23.985,43.641a2.9,2.9,0,0,0,3.553.555l.666-.333A33.892,33.892,0,0,1,32.647,53.3c8.55-3.664,8.884-12.326,7.218-18.322A48.227,48.227,0,0,0,53.3,3.221ZM34.424,9.772a6.439,6.439,0,1,1,9.106,9.106,6.368,6.368,0,0,1-9.106,0A6.467,6.467,0,0,1,34.424,9.772Z" transform="translate(0 0.005)" fill="#fff"/>
</g>
</g>
</svg>
<span>{{ title }} app is running!</span>
<svg id="rocket-smoke" xmlns="http://www.w3.org/2000/svg" width="516.119" height="1083.632" viewBox="0 0 516.119 1083.632">
<title>Rocket Ship Smoke</title>
<path id="Path_40" data-name="Path 40" d="M644.6,141S143.02,215.537,147.049,870.207s342.774,201.755,342.774,201.755S404.659,847.213,388.815,762.2c-27.116-145.51-11.551-384.124,271.9-609.1C671.15,139.365,644.6,141,644.6,141Z" transform="translate(-147.025 -140.939)" fill="#f5f5f5"/>
</svg>
<main>
<app-spinner></app-spinner>
<app-contact-sidebar></app-contact-sidebar>
<div class="main-content">
<app-navbar></app-navbar>
<router-outlet></router-outlet>
</div>
<!-- Resources -->
<h2>Resources</h2>
<p>Here are some links to help you get started:</p>
<div class="card-container">
<a class="card" target="_blank" rel="noopener" href="https://angular.io/tutorial">
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M5 13.18v4L12 21l7-3.82v-4L12 17l-7-3.82zM12 3L1 9l11 6 9-4.91V17h2V9L12 3z"/></svg>
<span>Learn Angular</span>
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg> </a>
<a class="card" target="_blank" rel="noopener" href="https://angular.io/cli">
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z"/></svg>
<span>CLI Documentation</span>
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>
</a>
<a class="card" target="_blank" rel="noopener" href="https://material.angular.io">
<svg xmlns="http://www.w3.org/2000/svg" style="margin-right: 8px" width="21.813" height="23.453" viewBox="0 0 179.2 192.7"><path fill="#ffa726" d="M89.4 0 0 32l13.5 118.4 75.9 42.3 76-42.3L179.2 32 89.4 0z"/><path fill="#fb8c00" d="M89.4 0v192.7l76-42.3L179.2 32 89.4 0z"/><path fill="#ffe0b2" d="m102.9 146.3-63.3-30.5 36.3-22.4 63.7 30.6-36.7 22.3z"/><path fill="#fff3e0" d="M102.9 122.8 39.6 92.2l36.3-22.3 63.7 30.6-36.7 22.3z"/><path fill="#fff" d="M102.9 99.3 39.6 68.7l36.3-22.4 63.7 30.6-36.7 22.4z"/></svg>
<span>Angular Material</span>
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>
</a>
<a class="card" target="_blank" rel="noopener" href="https://blog.angular.io/">
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M13.5.67s.74 2.65.74 4.8c0 2.06-1.35 3.73-3.41 3.73-2.07 0-3.63-1.67-3.63-3.73l.03-.36C5.21 7.51 4 10.62 4 14c0 4.42 3.58 8 8 8s8-3.58 8-8C20 8.61 17.41 3.8 13.5.67zM11.71 19c-1.78 0-3.22-1.4-3.22-3.14 0-1.62 1.05-2.76 2.81-3.12 1.77-.36 3.6-1.21 4.62-2.58.39 1.29.59 2.65.59 4.04 0 2.65-2.15 4.8-4.8 4.8z"/></svg>
<span>Angular Blog</span>
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>
</a>
<a class="card" target="_blank" rel="noopener" href="https://angular.io/devtools/">
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g><rect fill="none" height="24" width="24"/></g><g><g><path d="M14.73,13.31C15.52,12.24,16,10.93,16,9.5C16,5.91,13.09,3,9.5,3S3,5.91,3,9.5C3,13.09,5.91,16,9.5,16 c1.43,0,2.74-0.48,3.81-1.27L19.59,21L21,19.59L14.73,13.31z M9.5,14C7.01,14,5,11.99,5,9.5S7.01,5,9.5,5S14,7.01,14,9.5 S11.99,14,9.5,14z"/><polygon points="10.29,8.44 9.5,6 8.71,8.44 6.25,8.44 8.26,10.03 7.49,12.5 9.5,10.97 11.51,12.5 10.74,10.03 12.75,8.44"/></g></g></svg>
<span>Angular DevTools</span>
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>
</a>
</div>
<!-- Next Steps -->
<h2>Next Steps</h2>
<p>What do you want to do next with your app?</p>
<input type="hidden" #selection>
<div class="card-container">
<button class="card card-small" (click)="selection.value = 'component'" tabindex="0">
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
<span>New Component</span>
</button>
<button class="card card-small" (click)="selection.value = 'material'" tabindex="0">
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
<span>Angular Material</span>
</button>
<button class="card card-small" (click)="selection.value = 'pwa'" tabindex="0">
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
<span>Add PWA Support</span>
</button>
<button class="card card-small" (click)="selection.value = 'dependency'" tabindex="0">
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
<span>Add Dependency</span>
</button>
<button class="card card-small" (click)="selection.value = 'test'" tabindex="0">
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
<span>Run and Watch Tests</span>
</button>
<button class="card card-small" (click)="selection.value = 'build'" tabindex="0">
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
<span>Build for Production</span>
</button>
</div>
<!-- Terminal -->
<div class="terminal" [ngSwitch]="selection.value">
<pre *ngSwitchDefault>ng generate component xyz</pre>
<pre *ngSwitchCase="'material'">ng add @angular/material</pre>
<pre *ngSwitchCase="'pwa'">ng add @angular/pwa</pre>
<pre *ngSwitchCase="'dependency'">ng add _____</pre>
<pre *ngSwitchCase="'test'">ng test</pre>
<pre *ngSwitchCase="'build'">ng build</pre>
</div>
<!-- Links -->
<div class="card-container">
<a class="circle-link" title="Find a Local Meetup" href="https://www.meetup.com/find/?keywords=angular" target="_blank" rel="noopener">
<svg xmlns="http://www.w3.org/2000/svg" width="24.607" height="23.447" viewBox="0 0 24.607 23.447">
<title>Meetup Logo</title>
<path id="logo--mSwarm" d="M21.221,14.95A4.393,4.393,0,0,1,17.6,19.281a4.452,4.452,0,0,1-.8.069c-.09,0-.125.035-.154.117a2.939,2.939,0,0,1-2.506,2.091,2.868,2.868,0,0,1-2.248-.624.168.168,0,0,0-.245-.005,3.926,3.926,0,0,1-2.589.741,4.015,4.015,0,0,1-3.7-3.347,2.7,2.7,0,0,1-.043-.38c0-.106-.042-.146-.143-.166a3.524,3.524,0,0,1-1.516-.69A3.623,3.623,0,0,1,2.23,14.557a3.66,3.66,0,0,1,1.077-3.085.138.138,0,0,0,.026-.2,3.348,3.348,0,0,1-.451-1.821,3.46,3.46,0,0,1,2.749-3.28.44.44,0,0,0,.355-.281,5.072,5.072,0,0,1,3.863-3,5.028,5.028,0,0,1,3.555.666.31.31,0,0,0,.271.03A4.5,4.5,0,0,1,18.3,4.7a4.4,4.4,0,0,1,1.334,2.751,3.658,3.658,0,0,1,.022.706.131.131,0,0,0,.1.157,2.432,2.432,0,0,1,1.574,1.645,2.464,2.464,0,0,1-.7,2.616c-.065.064-.051.1-.014.166A4.321,4.321,0,0,1,21.221,14.95ZM13.4,14.607a2.09,2.09,0,0,0,1.409,1.982,4.7,4.7,0,0,0,1.275.221,1.807,1.807,0,0,0,.9-.151.542.542,0,0,0,.321-.545.558.558,0,0,0-.359-.534,1.2,1.2,0,0,0-.254-.078c-.262-.047-.526-.086-.787-.138a.674.674,0,0,1-.617-.75,3.394,3.394,0,0,1,.218-1.109c.217-.658.509-1.286.79-1.918a15.609,15.609,0,0,0,.745-1.86,1.95,1.95,0,0,0,.06-1.073,1.286,1.286,0,0,0-1.051-1.033,1.977,1.977,0,0,0-1.521.2.339.339,0,0,1-.446-.042c-.1-.092-.2-.189-.307-.284a1.214,1.214,0,0,0-1.643-.061,7.563,7.563,0,0,1-.614.512A.588.588,0,0,1,10.883,8c-.215-.115-.437-.215-.659-.316a2.153,2.153,0,0,0-.695-.248A2.091,2.091,0,0,0,7.541,8.562a9.915,9.915,0,0,0-.405.986c-.559,1.545-1.015,3.123-1.487,4.7a1.528,1.528,0,0,0,.634,1.777,1.755,1.755,0,0,0,1.5.211,1.35,1.35,0,0,0,.824-.858c.543-1.281,1.032-2.584,1.55-3.875.142-.355.28-.712.432-1.064a.548.548,0,0,1,.851-.24.622.622,0,0,1,.185.539,2.161,2.161,0,0,1-.181.621c-.337.852-.68,1.7-1.018,2.552a2.564,2.564,0,0,0-.173.528.624.624,0,0,0,.333.71,1.073,1.073,0,0,0,.814.034,1.22,1.22,0,0,0,.657-.655q.758-1.488,1.511-2.978.35-.687.709-1.37a1.073,1.073,0,0,1,.357-.434.43.43,0,0,1,.463-.016.373.373,0,0,1,.153.387.7.7,0,0,1-.057.236c-.065.157-.127.316-.2.469-.42.883-.846,1.763-1.262,2.648A2.463,2.463,0,0,0,13.4,14.607Zm5.888,6.508a1.09,1.09,0,0,0-2.179.006,1.09,1.09,0,0,0,2.179-.006ZM1.028,12.139a1.038,1.038,0,1,0,.01-2.075,1.038,1.038,0,0,0-.01,2.075ZM13.782.528a1.027,1.027,0,1,0-.011,2.055A1.027,1.027,0,0,0,13.782.528ZM22.21,6.95a.882.882,0,0,0-1.763.011A.882.882,0,0,0,22.21,6.95ZM4.153,4.439a.785.785,0,1,0,.787-.78A.766.766,0,0,0,4.153,4.439Zm8.221,18.22a.676.676,0,1,0-.677.666A.671.671,0,0,0,12.374,22.658ZM22.872,12.2a.674.674,0,0,0-.665.665.656.656,0,0,0,.655.643.634.634,0,0,0,.655-.644A.654.654,0,0,0,22.872,12.2ZM7.171-.123A.546.546,0,0,0,6.613.43a.553.553,0,1,0,1.106,0A.539.539,0,0,0,7.171-.123ZM24.119,9.234a.507.507,0,0,0-.493.488.494.494,0,0,0,.494.494.48.48,0,0,0,.487-.483A.491.491,0,0,0,24.119,9.234Zm-19.454,9.7a.5.5,0,0,0-.488-.488.491.491,0,0,0-.487.5.483.483,0,0,0,.491.479A.49.49,0,0,0,4.665,18.936Z" transform="translate(0 0.123)" fill="#f64060"/>
</svg>
</a>
<a class="circle-link" title="Join the Conversation on Discord" href="https://discord.gg/angular" target="_blank" rel="noopener">
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 245 240">
<title>Discord Logo</title>
<path d="M104.4 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1.1-6.1-4.5-11.1-10.2-11.1zM140.9 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1s-4.5-11.1-10.2-11.1z"/>
<path d="M189.5 20h-134C44.2 20 35 29.2 35 40.6v135.2c0 11.4 9.2 20.6 20.5 20.6h113.4l-5.3-18.5 12.8 11.9 12.1 11.2 21.5 19V40.6c0-11.4-9.2-20.6-20.5-20.6zm-38.6 130.6s-3.6-4.3-6.6-8.1c13.1-3.7 18.1-11.9 18.1-11.9-4.1 2.7-8 4.6-11.5 5.9-5 2.1-9.8 3.5-14.5 4.3-9.6 1.8-18.4 1.3-25.9-.1-5.7-1.1-10.6-2.7-14.7-4.3-2.3-.9-4.8-2-7.3-3.4-.3-.2-.6-.3-.9-.5-.2-.1-.3-.2-.4-.3-1.8-1-2.8-1.7-2.8-1.7s4.8 8 17.5 11.8c-3 3.8-6.7 8.3-6.7 8.3-22.1-.7-30.5-15.2-30.5-15.2 0-32.2 14.4-58.3 14.4-58.3 14.4-10.8 28.1-10.5 28.1-10.5l1 1.2c-18 5.2-26.3 13.1-26.3 13.1s2.2-1.2 5.9-2.9c10.7-4.7 19.2-6 22.7-6.3.6-.1 1.1-.2 1.7-.2 6.1-.8 13-1 20.2-.2 9.5 1.1 19.7 3.9 30.1 9.6 0 0-7.9-7.5-24.9-12.7l1.4-1.6s13.7-.3 28.1 10.5c0 0 14.4 26.1 14.4 58.3 0 0-8.5 14.5-30.6 15.2z"/>
</svg>
</a>
</div>
<!-- Footer -->
<footer>
Love Angular?&nbsp;
<a href="https://github.com/angular/angular" target="_blank" rel="noopener"> Give our repo a star.
<div class="github-star-badge">
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/></svg>
Star
</div>
</a>
<a href="https://github.com/angular/angular" target="_blank" rel="noopener">
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" fill="#1976d2"/><path d="M0 0h24v24H0z" fill="none"/></svg>
</a>
</footer>
<svg id="clouds" xmlns="http://www.w3.org/2000/svg" width="2611.084" height="485.677" viewBox="0 0 2611.084 485.677">
<title>Gray Clouds Background</title>
<path id="Path_39" data-name="Path 39" d="M2379.709,863.793c10-93-77-171-168-149-52-114-225-105-264,15-75,3-140,59-152,133-30,2.83-66.725,9.829-93.5,26.25-26.771-16.421-63.5-23.42-93.5-26.25-12-74-77-130-152-133-39-120-212-129-264-15-54.084-13.075-106.753,9.173-138.488,48.9-31.734-39.726-84.4-61.974-138.487-48.9-52-114-225-105-264,15a162.027,162.027,0,0,0-103.147,43.044c-30.633-45.365-87.1-72.091-145.206-58.044-52-114-225-105-264,15-75,3-140,59-152,133-53,5-127,23-130,83-2,42,35,72,70,86,49,20,106,18,157,5a165.625,165.625,0,0,0,120,0c47,94,178,113,251,33,61.112,8.015,113.854-5.72,150.492-29.764a165.62,165.62,0,0,0,110.861-3.236c47,94,178,113,251,33,31.385,4.116,60.563,2.495,86.487-3.311,25.924,5.806,55.1,7.427,86.488,3.311,73,80,204,61,251-33a165.625,165.625,0,0,0,120,0c51,13,108,15,157-5a147.188,147.188,0,0,0,33.5-18.694,147.217,147.217,0,0,0,33.5,18.694c49,20,106,18,157,5a165.625,165.625,0,0,0,120,0c47,94,178,113,251,33C2446.709,1093.793,2554.709,922.793,2379.709,863.793Z" transform="translate(142.69 -634.312)" fill="#eee"/>
</svg>
</div>
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
<!-- * * * * * * * * * * * The content above * * * * * * * * * * * -->
<!-- * * * * * * * * * * is only a placeholder * * * * * * * * * * -->
<!-- * * * * * * * * * * and can be replaced. * * * * * * * * * * * -->
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
<!-- * * * * * * * * * * End of Placeholder * * * * * * * * * * * -->
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
</main>
+7 -16
View File
@@ -1,27 +1,18 @@
import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(() => TestBed.configureTestingModule({
declarations: [AppComponent]
}));
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AppComponent, HttpClientTestingModule, RouterTestingModule]
}).compileComponents();
});
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it(`should have as title 'my-portfolio'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app.title).toEqual('my-portfolio');
});
it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('.content span')?.textContent).toContain('my-portfolio app is running!');
});
});
+10 -2
View File
@@ -1,10 +1,18 @@
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { NavbarComponent } from './navbar/navbar.component';
import { ContactSidebarComponent } from './contact-sidebar/contact-sidebar.component';
import { SpinnerComponent } from './spinner/spinner.component';
import { HttpClientModule } from '@angular/common/http';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
styleUrls: ['./app.component.scss'],
standalone: true,
imports: [RouterOutlet, NavbarComponent, ContactSidebarComponent, SpinnerComponent]
})
export class AppComponent {
title = 'my-portfolio';
constructor() {}
}
-16
View File
@@ -1,16 +0,0 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
+19
View File
@@ -0,0 +1,19 @@
import { environment } from "src/environments/environment";
import { ICv } from "../models/cv.model";
import { CvService } from "../services/cv.service";
export abstract class BaseComponent<T extends object> {
public model: T = <T>{};
candidateId: number = 1;
imagesOrigin: string = environment.apiUrl + '/images/';
constructor(public svc: CvService) {
}
assignData(response: Partial<ICv> | unknown){
Object.assign(this.model, response);
if(this.svc.candidateAndSocialLinks == null){
this.svc.getCandidateInfoSubject.next("");
}
}
}
+42
View File
@@ -0,0 +1,42 @@
<article class="blog" data-page="blog">
<header>
<h2 class="h2 article-title">Posts</h2>
</header>
<section class="blog-posts">
<ul class="blog-posts-list">
<li class="blog-post-item" *ngFor="let post of model.posts">
<a [href]="post.postUrl" target="_blank">
<figure class="blog-banner-box" *ngIf="post.image">
<img src="{{blogUrl + post.image}}" [alt]="post.title" loading="lazy">
</figure>
<div class="blog-content">
<div class="blog-meta">
<p class="blog-category" *ngFor="let category of post.categories; index as i">{{post.categories.length - i > 1 ? category + ',' : category}}</p>
<span class="dot" *ngIf="post.categories.length > 0"></span>
<time>{{post.createdDate}}</time>
</div>
<h3 class="h3 blog-item-title">{{post.title}}</h3>
<p class="blog-text">
{{post.description}}
</p>
</div>
</a>
</li>
</ul>
</section>
</article>
View File
+24
View File
@@ -0,0 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { BlogComponent } from './blog.component';
describe('BlogComponent', () => {
let component: BlogComponent;
let fixture: ComponentFixture<BlogComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [BlogComponent, HttpClientTestingModule]
})
.compileComponents();
fixture = TestBed.createComponent(BlogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
+32
View File
@@ -0,0 +1,32 @@
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CvService } from '../services/cv.service';
import { BaseComponent } from '../base/base.component';
import { IBlog } from './blog.model';
import { environment } from 'src/environments/environment';
@Component({
selector: 'app-blog',
templateUrl: './blog.component.html',
styleUrl: './blog.component.scss',
standalone: true,
imports: [CommonModule]
})
export class BlogComponent extends BaseComponent<IBlog> implements OnInit{
blogUrl!: string;
constructor(svc: CvService){
super(svc);
}
ngOnInit(): void {
this.getBlog();
}
getBlog(){
this.svc.getBlog(this.candidateId).subscribe((response: IBlog) => {
this.svc.blog = this.svc.blog ?? response;
this.assignData(response);
this.blogUrl = this.svc.blog.blogUrl;
});
}
}
+6
View File
@@ -0,0 +1,6 @@
import { IPost } from "../models/post.model";
export interface IBlog{
blogUrl: string;
posts: IPost[];
}
@@ -0,0 +1,112 @@
<aside class="sidebar" [ngClass]="sideBarExpanded ? 'active' : ''" data-sidebar>
<div class="sidebar-info" ng-init="let displayName = ">
<figure class="avatar-box">
<img src="./assets/images/my-pic.png" alt="{{model.candidate?.displayName}}" width="80">
</figure>
<div class="info-content">
<h1 class="name" title="{{model.candidate?.displayName}}">{{model.candidate?.displayName}}</h1>
<p class="title">{{model.title}}</p>
</div>
<button class="info_more-btn" (click)="sideBarExpanded = !sideBarExpanded" data-sidebar-btn>
<span>Show Contacts</span>
<i class="fa-regular fa-chevron-down"></i>
</button>
</div>
<div class="sidebar-info_more">
<div class="separator"></div>
<ul class="contacts-list">
<li class="contact-item">
<div class="icon-box">
<i class="fa-light fa-envelope"></i>
</div>
<div class="contact-info">
<p class="contact-title">Email</p>
<a href="mailto:{{model.candidate?.email}}" class="contact-link">{{model.candidate?.email}}</a>
</div>
</li>
<li class="contact-item">
<div class="icon-box">
<i class="fa-light fa-mobile-notch"></i>
</div>
<div class="contact-info">
<p class="contact-title">Phone</p>
<a href="tel:{{model.candidate?.phone}}" class="contact-link">{{model.candidate?.phone}}</a>
</div>
</li>
<li class="contact-item">
<div class="icon-box">
<i class="fa-regular fa-cake-candles"></i>
</div>
<div class="contact-info">
<p class="contact-title">Birthday</p>
<time>{{model.candidate?.dob}}</time>
</div>
</li>
<li class="contact-item">
<div class="icon-box">
<i class="fa-light fa-location-dot"></i>
</div>
<div class="contact-info">
<p class="contact-title">Location</p>
<address>{{model.candidate?.address}}</address>
</div>
</li>
</ul>
<div class="separator"></div>
<ul class="social-list">
<li class="social-item" *ngIf="model.socialLinks?.linkedin">
<a href="{{model.socialLinks?.linkedin}}" target="_blank" class="social-link">
<i class="fa-brands fa-linkedin"></i>
</a>
</li>
<li class="social-item" *ngIf="model.socialLinks?.gitHub">
<a href="{{model.socialLinks?.gitHub}}" target="_blank" class="social-link">
<i class="fa-brands fa-github"></i>
</a>
</li>
<li class="social-item" *ngIf="model.socialLinks?.blogUrl">
<a href="{{model.socialLinks?.blogUrl}}" target="_blank" class="social-link">
<i class="fa-duotone fa-blog"></i>
</a>
</li>
</ul>
</div>
</aside>
@@ -0,0 +1,3 @@
img {
border-radius: inherit;
}
@@ -0,0 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ContactSidebarComponent } from './contact-sidebar.component';
describe('ContactSidebarComponent', () => {
let component: ContactSidebarComponent;
let fixture: ComponentFixture<ContactSidebarComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ContactSidebarComponent, HttpClientTestingModule]
})
.compileComponents();
fixture = TestBed.createComponent(ContactSidebarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,34 @@
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { BaseComponent } from '../base/base.component';
import { CvService } from '../services/cv.service';
import { ISideBar } from './side-bar.model';
@Component({
selector: 'app-contact-sidebar',
templateUrl: './contact-sidebar.component.html',
styleUrl: './contact-sidebar.component.scss',
standalone: true,
imports: [CommonModule]
})
export class ContactSidebarComponent extends BaseComponent<ISideBar> implements OnInit {
sideBarExpanded: boolean = false;
displayName!: string;
constructor(svc: CvService){
super(svc);
}
ngOnInit(): void {
this.svc.getCandidateInfoSubject.subscribe(() => {
this.getCandidateAndSocialLinks();
});
this.getCandidateAndSocialLinks();
}
getCandidateAndSocialLinks(){
this.svc.getCandidateWithSocialLinks(this.candidateId).subscribe((response: ISideBar) => {
this.svc.candidateAndSocialLinks = this.svc.candidateAndSocialLinks ?? response;
this.assignData(response);
});
}
}
@@ -0,0 +1,8 @@
import { ICandidate } from "../models/candidate.model";
import { ISocialLinks } from "../models/social-links.model";
export interface ISideBar{
title: string;
candidate?: ICandidate;
socialLinks?: ISocialLinks;
}
+53
View File
@@ -0,0 +1,53 @@
<article class="contact" data-page="contact">
<header>
<h2 class="h2 article-title">Contact</h2>
</header>
<section class="mapbox" data-mapbox>
<figure>
<iframe
src="https://maps.google.com/maps?width=520&amp;height=400&amp;hl=en&amp;q=Main%20Street%20Samalkot+(My%20City)&amp;t=&amp;z=12&amp;ie=UTF8&amp;iwloc=B&amp;output=embed"
width="400" height="300" loading="lazy"></iframe>
</figure>
</section>
<section class="contact-form">
<h3 class="h3 form-title">Contact Form</h3>
<form class="form" #messageForm="ngForm" (ngSubmit)="sendMessage(messageForm)" autocomplete="off">
<div class="input-wrapper">
<div>
<input type="text" name="fullname" maxlength="20" #fullName="ngModel" autocomplete="new" [(ngModel)]="messageModel.name" class="form-input" placeholder="Full name" required>
<em class="error" *ngIf="(fullName.errors?.['required']) && fullName.touched">Full name is required</em>
</div>
<div>
<input type="email" name="email" email #email="ngModel" autocomplete="new" [(ngModel)]="messageModel.email" class="form-input" placeholder="Email address" required>
<em class="error" *ngIf="(email.errors?.['email'] && !email.errors?.['required']) && email.touched">Must be a valid email format</em>
<em class="error" *ngIf="(email.errors?.['required']) && email.touched">Email is required</em>
</div>
</div>
<div class="text-area">
<textarea name="message" #message="ngModel" autocomplete="off" maxlength="500" [(ngModel)]="messageModel.content" class="form-input" placeholder="Your Message" required></textarea>
<em class="error" *ngIf="(message.errors?.['required']) && message.touched">Message is required</em>
</div>
<button class="form-btn" type="submit" [disabled]="messageForm.form.invalid && submitted">
<i class="fa-regular fa-paper-plane"></i>
<span>Send Message</span>
</button>
<div class="messageSentError" *ngIf="messageSentError">
Message not sent. Please try again.
</div>
<div class="messageSentSuccess" *ngIf="messageSentSuccess">
Message sent successfully.
</div>
</form>
</section>
</article>
+20
View File
@@ -0,0 +1,20 @@
.messageSentError {
margin:25px 0;
color: rgb(206, 87, 7);
font-size: 18px;
text-align: center;
}
.messageSentSuccess{
margin:25px 0;
color: green;
font-size: 18px;
text-align: center;
}
em.error{
padding: 2px 5px;
color: rgb(206, 87, 7);
//color: white;
font-style: normal;
}
+24
View File
@@ -0,0 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ContactComponent } from './contact.component';
describe('ContactComponent', () => {
let component: ContactComponent;
let fixture: ComponentFixture<ContactComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ContactComponent, HttpClientTestingModule]
})
.compileComponents();
fixture = TestBed.createComponent(ContactComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
+53
View File
@@ -0,0 +1,53 @@
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, FormBuilder, FormControl, NgForm } from '@angular/forms';
import { CvService } from '../services/cv.service';
import { IContact } from './contact.model';
@Component({
selector: 'app-contact',
templateUrl: './contact.component.html',
styleUrl: './contact.component.scss',
standalone: true,
imports: [CommonModule, FormsModule]
})
export class ContactComponent {
messageModel: IContact = {name: '', email: '', content: '' };
candidateId: number = 1;
messageSentError: boolean = false;
messageSentSuccess: boolean = false;
submitted: boolean = false;
constructor(private svc: CvService, private formBuilder: FormBuilder){
}
sendMessage(messageForm: NgForm) :void{
this.submitted = true;
if(messageForm.valid){
this.messageSentError = false;
this.messageSentSuccess = false;
this.svc.sendMessage(this.messageModel, this.candidateId).subscribe({
next: (response) => this.messageSentSuccess = response,
error: () => this.messageSentError = true
});
}
else{
this.validateAllFormFields(messageForm);
}
}
validateAllFormFields(formGroup: NgForm) {
Object.keys(formGroup.controls).forEach(field => {
const control = formGroup.controls[field];
if (control instanceof FormControl) {
control.markAsTouched({ onlySelf: true });
} else if (control instanceof NgForm) {
this.validateAllFormFields(control);
}
});
}
}
+5
View File
@@ -0,0 +1,5 @@
export interface IContact{
name: string;
email: string;
content: string;
}
@@ -0,0 +1,14 @@
import { HttpInterceptorFn } from "@angular/common/http";
import { inject } from "@angular/core";
import { AuthService } from "../services/auth.service";
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const authSvc = inject(AuthService);
const apiKey = authSvc.getApiKey();
const authReq = req.clone({
headers: req.headers.set('XApiKey', apiKey)
});
return next(authReq);
};
+4
View File
@@ -0,0 +1,4 @@
import { authInterceptor } from "./auth.interceptor";
import { loadingInterceptor } from "./loading.interceptor";
export const httpInterceptors = [authInterceptor, loadingInterceptor];
@@ -0,0 +1,22 @@
import { HttpInterceptorFn } from "@angular/common/http";
import { inject } from "@angular/core";
import { finalize } from "rxjs";
import { LoaderService } from "../services/loader.service";
let totalRequests: number = 0;
export const loadingInterceptor: HttpInterceptorFn = (req, next) => {
const loadingSvc = inject(LoaderService);
totalRequests++;
loadingSvc.setLoading(true);
return next(req).pipe(
finalize(() => {
totalRequests--;
if (totalRequests === 0) {
loadingSvc.setLoading(false);
}
})
);
};
+9
View File
@@ -0,0 +1,9 @@
export interface IAcademic{
academicId: number;
institution: string;
startYear: number;
endYear: number;
degree: string;
period: string;
degreeSpecialization: string;
}
+10
View File
@@ -0,0 +1,10 @@
export interface ICandidate{
firstName: string;
lastName: string;
dob: string;
email: string;
phone: string;
address: string;
avatar: string;
displayName: string;
}
+23
View File
@@ -0,0 +1,23 @@
import { IAcademic } from "./academic.model";
import { ICandidate } from "./candidate.model";
import { IExperience } from "./experience.model";
import { IHobby } from "./hobby.model";
import { IPost } from "./post.model";
import { IProject } from "./project.model";
import { ISkill } from "./skill.model";
import { ISocialLinks } from "./social-links.model";
export interface ICv{
resumeId: number;
title: string;
about: string;
candidate: ICandidate;
socialLinks: ISocialLinks;
posts: IPost[];
academics: IAcademic[];
skills: ISkill[];
experiences: IExperience[];
hobbies: IHobby[];
projects: IProject[];
projectsCategories: string[];
}
@@ -0,0 +1,5 @@
export interface IExperienceDetails{
id: number;
details: string;
order: number;
}
+12
View File
@@ -0,0 +1,12 @@
import { IExperienceDetails } from "./experience-details.model";
export interface IExperience{
experienceId: number;
title: string;
description: string;
company: string;
startYear: string;
endYear: string;
period: string;
details: IExperienceDetails[];
}
+6
View File
@@ -0,0 +1,6 @@
export interface IHobby{
hobbyId: number;
name: string;
description: string;
icon: string;
}
+14
View File
@@ -0,0 +1,14 @@
export interface IPost{
postId: number;
slug: string;
title: string;
description: string;
categories: string[];
postUrl: string;
likes: number;
views: number;
comments: number;
image: string;
createdDate: string;
modifiedDate: string;
}
+18
View File
@@ -0,0 +1,18 @@
export interface IProject{
projectId: number;
name: string;
description: string;
categories: string[];
categoryList: string[];
roles: string[];
responsibilities: string[];
technologiesUsed: string[];
imagePath: string;
challenges: string;
lessonsLearned: string;
impact: string;
startDate: string;
endDate: string;
status: string;
resumeId: number;
}
+6
View File
@@ -0,0 +1,6 @@
export interface ISkill{
skillId: number;
name: string;
description: string;
proficiencyLevel: number;
}
+6
View File
@@ -0,0 +1,6 @@
export interface ISocialLinks{
id: number;
gitHub: string;
linkedin: string;
blogUrl: string;
}
+27
View File
@@ -0,0 +1,27 @@
<nav class="navbar">
<ul class="navbar-list">
<li class="navbar-item">
<button routerLink="" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }" class="navbar-link" data-nav-link>About</button>
</li>
<li class="navbar-item">
<button routerLink="/resume" routerLinkActive="active" class="navbar-link" data-nav-link>Resume</button>
</li>
<li class="navbar-item">
<button routerLink="/projects" routerLinkActive="active" class="navbar-link" data-nav-link>Projects</button>
</li>
<li class="navbar-item">
<button routerLink="/weblog" routerLinkActive="active" class="navbar-link" data-nav-link>Blog</button>
</li>
<li class="navbar-item">
<button routerLink="/contact" routerLinkActive="active" class="navbar-link" data-nav-link>Contact</button>
</li>
</ul>
</nav>
+24
View File
@@ -0,0 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { NavbarComponent } from './navbar.component';
describe('NavbarComponent', () => {
let component: NavbarComponent;
let fixture: ComponentFixture<NavbarComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [NavbarComponent, RouterTestingModule]
})
.compileComponents();
fixture = TestBed.createComponent(NavbarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
+13
View File
@@ -0,0 +1,13 @@
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
@Component({
selector: 'app-navbar',
templateUrl: './navbar.component.html',
styleUrl: './navbar.component.scss',
standalone: true,
imports: [CommonModule, RouterModule]
})
export class NavbarComponent {
}
@@ -0,0 +1,101 @@
<div class="project-detail" role="dialog" aria-labelledby="project-title">
<div class="detail-header">
<h2 id="project-title">{{ project.name }}</h2>
<button class="close-btn" (click)="close()" aria-label="Close">
<i class="fa-solid fa-xmark"></i>
</button>
</div>
@if (project.status) {
<span class="status-badge">{{ project.status }}</span>
}
@if (project.description) {
<div class="detail-section">
<h4>Description</h4>
<p>{{ project.description }}</p>
</div>
}
@if (project.roles.length) {
<div class="detail-section">
<h4>Roles</h4>
<div class="tag-list">
@for (role of project.roles; track role) {
<span class="tag">{{ role }}</span>
}
</div>
</div>
}
@if (project.responsibilities.length) {
<div class="detail-section">
<h4>Responsibilities</h4>
<div class="tag-list">
@for (r of project.responsibilities; track r) {
<span class="tag">{{ r }}</span>
}
</div>
</div>
}
@if (project.technologiesUsed.length) {
<div class="detail-section">
<h4>Technologies</h4>
<div class="tag-list">
@for (tech of project.technologiesUsed; track tech) {
<span class="tag tech">{{ tech }}</span>
}
</div>
</div>
}
@if (project.startDate || project.endDate) {
<div class="detail-section">
<h4>Duration</h4>
<p class="duration">
@if (project.startDate) {
<span>{{ project.startDate | date: 'MMM yyyy' }}</span>
}
@if (project.startDate && project.endDate) {
<span></span>
}
@if (project.endDate) {
<span>{{ project.endDate | date: 'MMM yyyy' }}</span>
}
</p>
</div>
}
@if (project.challenges) {
<div class="detail-section">
<h4>Challenges</h4>
<p>{{ project.challenges }}</p>
</div>
}
@if (project.lessonsLearned) {
<div class="detail-section">
<h4>Lessons Learned</h4>
<p>{{ project.lessonsLearned }}</p>
</div>
}
@if (project.impact) {
<div class="detail-section">
<h4>Impact</h4>
<p>{{ project.impact }}</p>
</div>
}
@if (project.categories.length) {
<div class="detail-section">
<h4>Categories</h4>
<div class="tag-list">
@for (cat of project.categories; track cat) {
<span class="tag category">{{ cat }}</span>
}
</div>
</div>
}
</div>
@@ -0,0 +1,138 @@
.project-detail {
padding: 24px;
color: var(--white-1, #fff);
max-height: 80vh;
overflow-y: auto;
background: var(--eerie-black-2, #1e1e1e);
border: 1.2px solid hsla(45, 100%, 72%, 0.45);
border-radius: 14px;
}
/* Override Material Dialog container defaults - must be global due to ViewEncapsulation.None */
.dark-popup-panel {
border-radius: 14px !important;
overflow: hidden !important;
.mat-dialog-container {
background: var(--eerie-black-2, #1e1e1e) !important;
border-radius: 14px !important;
overflow: hidden !important;
border: 2px solid rgba(227, 179, 65, 0.9) !important;
box-shadow:
inset 0 0 0 1px rgba(255, 255, 255, 0.08),
0 8px 32px rgba(0, 0, 0, 0.5) !important;
padding: 0 !important;
/* Ensure no white background shows */
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 14px;
pointer-events: none;
background: var(--eerie-black-2, #1e1e1e);
z-index: -1;
}
}
}
.detail-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
margin-bottom: 12px;
h2 {
margin: 0;
font-size: 1.35rem;
color: var(--white-1, #fff);
line-height: 1.3;
}
}
.close-btn {
background: transparent;
border: none;
color: var(--light-gray-70, #aaa);
font-size: 1.1rem;
cursor: pointer;
padding: 4px 6px;
border-radius: 6px;
transition: background 0.2s, color 0.2s;
flex-shrink: 0;
&:hover {
background: rgba(255, 255, 255, 0.08);
color: var(--white-1, #fff);
}
}
.status-badge {
display: inline-block;
background: rgba(227, 179, 65, 0.15);
color: var(--orange-yellow-crayola, #e3b341);
font-size: 0.75rem;
font-weight: 600;
padding: 3px 10px;
border-radius: 12px;
margin-bottom: 16px;
letter-spacing: 0.3px;
}
.detail-section {
margin-bottom: 16px;
h4 {
margin: 0 0 6px;
font-size: 0.8rem;
font-weight: 600;
color: var(--light-gray-70, #999);
text-transform: uppercase;
letter-spacing: 0.5px;
}
p {
margin: 0;
font-size: 0.9rem;
line-height: 1.55;
color: var(--white-2, #ddd);
white-space: pre-line;
}
}
.duration {
color: var(--white-2, #ddd);
font-size: 0.9rem;
}
.tag-list {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.tag {
display: inline-block;
padding: 4px 10px;
border-radius: 6px;
font-size: 0.78rem;
background: rgba(255, 255, 255, 0.06);
color: var(--white-2, #ddd);
border: 1px solid rgba(255, 255, 255, 0.08);
&.tech {
background: rgba(100, 180, 255, 0.1);
color: #8cc4ff;
border-color: rgba(100, 180, 255, 0.15);
}
&.category {
background: rgba(227, 179, 65, 0.1);
color: var(--orange-yellow-crayola, #e3b341);
border-color: rgba(227, 179, 65, 0.15);
}
}
@@ -0,0 +1,21 @@
import { Component, inject, ViewEncapsulation } from '@angular/core';
import { CommonModule, DatePipe } from '@angular/common';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { IProject } from '../../models/project.model';
@Component({
selector: 'app-project-detail-popup',
templateUrl: './project-detail-popup.html',
styleUrls: ['./project-detail-popup.scss'],
standalone: true,
imports: [CommonModule, DatePipe],
encapsulation: ViewEncapsulation.None
})
export class ProjectDetailPopupComponent {
private dialogRef = inject(MatDialogRef<ProjectDetailPopupComponent>);
project: IProject = inject(MAT_DIALOG_DATA);
close(): void {
this.dialogRef.close();
}
}
+74
View File
@@ -0,0 +1,74 @@
<article class="projects" data-page="projects">
<header>
<h2 class="h2 article-title">Projects</h2>
</header>
<section class="projects">
<ul class="filter-list">
<li class="filter-item">
<button [ngClass]="{active: filter === 'All'}" (click)="filterProjects('All')">All</button>
</li>
<li class="filter-item" *ngFor="let category of model.projectsCategories">
<button (click)="filterProjects(category)" [ngClass]="{active: filter === category}"
(click)="filter = category">{{category}}</button>
</li>
</ul>
<div class="filter-select-box" (click)="categoryClicked = !categoryClicked">
<button class="filter-select" [ngClass]="{active: categoryClicked}">
<div class="select-value">{{filter}}</div>
<div class="select-icon">
<i class="fa-regular fa-chevron-down"></i>
</div>
</button>
<ul class="select-list">
<li class="select-item">
<button (click)="filterProjects('All')">All</button>
</li>
<li class="select-item" *ngFor="let category of model.projectsCategories">
<button (click)="filterProjects(category)">{{category}}</button>
</li>
</ul>
</div>
<ul class="project-list">
<li class="project-item active" *ngFor="let project of projects">
<a>
<figure class="project-img">
<button class="project-item-icon-box" (click)="openViewProject(project)"
(keyup.enter)="openViewProject(project)" title="View Project">
<i class="fa-regular fa-eye"></i>
</button>
<img src="{{imagesOrigin + project.imagePath}}" loading="lazy">
</figure>
<h3 class="project-title">{{project.name}}</h3>
<div class="project-category">
<span *ngFor="let responsbility of project.responsibilities; index as i" class="inline">{{i > 0 ? ', ' +
responsbility : responsbility}}</span>
</div>
</a>
</li>
</ul>
</section>
</article>
+7
View File
@@ -0,0 +1,7 @@
.inline{
display: inline;
}
.no-margin{
margin-left: 0px;
}
@@ -0,0 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ProjectsComponent } from './projects.component';
describe('ProjectsComponent', () => {
let component: ProjectsComponent;
let fixture: ComponentFixture<ProjectsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ProjectsComponent, HttpClientTestingModule]
})
.compileComponents();
fixture = TestBed.createComponent(ProjectsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
+58
View File
@@ -0,0 +1,58 @@
import { CommonModule } from '@angular/common';
import { Component, OnInit, Inject } from '@angular/core';
import { BaseComponent } from '../base/base.component';
import { CvService } from '../services/cv.service';
import { IProjects } from './projects.model';
import { IProject } from '../models/project.model';
import { HttpClient } from '@angular/common/http';
import { Subscription } from 'rxjs';
import { ProjectDetailPopupComponent } from './project-detail-popup/project-detail-popup';
import { MatDialog} from '@angular/material/dialog';
@Component({
selector: 'app-projects',
templateUrl: './projects.component.html',
styleUrl: './projects.component.scss',
standalone: true,
imports: [CommonModule]
})
export class ProjectsComponent extends BaseComponent<IProjects> implements OnInit {
filter: string = 'All';
projects!: IProject[];
subscription: Subscription = <Subscription>{};
categoryClicked: boolean = false;
constructor(svc: CvService, public http: HttpClient, @Inject(MatDialog) private dialog: MatDialog){
super(svc);
}
ngOnInit(): void {
this.getProjects();
}
getProjects(){
this.svc.getProjects(this.candidateId).subscribe((response: IProjects) => {
this.svc.projects = this.svc.projects ?? response;
this.projects = response.projects;
this.assignData(response);
});
}
filterProjects(category: string) {
this.filter = category;
this.projects = this.filter === 'All'
? this.model.projects
: this.model.projects.filter(
(project: IProject) => {
return project.categories.includes(category)
});
}
openViewProject(project: IProject): void {
this.dialog.open(ProjectDetailPopupComponent, {
data: project,
panelClass: 'dark-popup-panel',
width: '520px',
maxHeight: '85vh'
});
}
}
+6
View File
@@ -0,0 +1,6 @@
import { IProject } from "../models/project.model";
export interface IProjects{
projects: IProject[];
projectsCategories: string[];
}
+86
View File
@@ -0,0 +1,86 @@
<article class="resume" data-page="resume">
<header>
<h2 class="h2 article-title">Resume</h2>
</header>
<section class="timeline">
<div class="title-wrapper">
<div class="icon-box">
<i class="fa-light fa-book-open"></i>
</div>
<h3 class="h3">Education</h3>
</div>
<ol class="timeline-list">
<li class="timeline-item" *ngFor="let education of model.academics">
<h4 class="h4 timeline-item-title">{{education.degree}}{{education.degreeSpecialization != null ? " - " + education.degreeSpecialization : ""}}</h4>
<span>{{education.period}}</span>
<p class="timeline-text">
{{education.institution}}
</p>
</li>
</ol>
</section>
<section class="timeline">
<div class="title-wrapper">
<div class="icon-box">
<i class="fa-light fa-briefcase"></i>
</div>
<h3 class="h3">Experience</h3>
</div>
<ol class="timeline-list">
<li class="timeline-item" *ngFor="let experience of model.experiences">
<h4 class="h4 timeline-item-title">{{experience.title}}</h4>
<span>{{experience.period}}</span>
<p class="timeline-text">
{{experience.company}}
</p>
</li>
</ol>
</section>
<section class="skill">
<h3 class="h3 skills-title">My skills</h3>
<ul class="skills-list content-card">
<li class="skills-item" *ngFor="let skill of model.skills">
<div class="title-wrapper">
<h5 class="h5">{{skill.name}}</h5>
<data value="{{skill.proficiencyLevel}}">{{skill.proficiencyLevel}}%</data>
</div>
<div class="skill-progress-bg">
<div class="skill-progress-fill" [style]="'width: ' + skill.proficiencyLevel + '%'"></div>
</div>
</li>
</ul>
</section>
</article>
+24
View File
@@ -0,0 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ResumeComponent } from './resume.component';
describe('ResumeComponent', () => {
let component: ResumeComponent;
let fixture: ComponentFixture<ResumeComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ResumeComponent, HttpClientTestingModule]
})
.compileComponents();
fixture = TestBed.createComponent(ResumeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
+29
View File
@@ -0,0 +1,29 @@
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { BaseComponent } from '../base/base.component';
import { IResume } from './resume.model';
import { CvService } from '../services/cv.service';
@Component({
selector: 'app-resume',
templateUrl: './resume.component.html',
styleUrl: './resume.component.scss',
standalone: true,
imports: [CommonModule]
})
export class ResumeComponent extends BaseComponent<IResume> implements OnInit {
constructor(svc: CvService){
super(svc);
}
ngOnInit(): void {
this.getResume();
}
getResume(){
this.svc.getResume(this.candidateId).subscribe((response: IResume) => {
this.svc.resume = this.svc.resume ?? response;
this.assignData(response);
});
}
}
+9
View File
@@ -0,0 +1,9 @@
import { IAcademic } from "../models/academic.model";
import { IExperience } from "../models/experience.model";
import { ISkill } from "../models/skill.model";
export interface IResume{
academics?: IAcademic[];
experiences?: IExperience[];
skills?: ISkill[];
}
+16
View File
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { AuthService } from './auth.service';
describe('AuthService', () => {
let service: AuthService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(AuthService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
+12
View File
@@ -0,0 +1,12 @@
import { Injectable } from '@angular/core';
import { environment } from 'src/environments/environment';
@Injectable({
providedIn: 'root'
})
export class AuthService {
getApiKey(): string{
return environment.apiKey;
}
}
+19
View File
@@ -0,0 +1,19 @@
import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { CvService } from './cv.service';
describe('CvService', () => {
let service: CvService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule]
});
service = TestBed.inject(CvService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
+73
View File
@@ -0,0 +1,73 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { ICv } from '../models/cv.model';
import { Observable, Subject, of } from 'rxjs';
import { IAbout } from '../about/about.model';
import { IResume } from '../resume/resume.model';
import { IBlog } from '../blog/blog.model';
import { IProjects } from '../projects/projects.model';
import { ISideBar } from '../contact-sidebar/side-bar.model';
import { IContact } from '../contact/contact.model';
@Injectable({
providedIn: 'root'
})
export class CvService {
public getCandidateInfoSubject = new Subject();
public cv!: ICv;
public about!: IAbout;
public candidateAndSocialLinks!: ISideBar;
public resume!: IResume;
public projects!: IProjects;
public blog!: IBlog;
constructor(private http: HttpClient) { }
getCv(candidateId: number): Observable<ICv> {
if(this.cv != null){
return of(this.cv);
}
return this.http.get<ICv>(`/api/v0.1/cv/${candidateId}`);
}
getHobbies(candidateId: number): Observable<IAbout>{
if(this.about != null){
return of(this.about);
}
return this.http.get<IAbout>(`/api/v1/cv/GetHobbies/${candidateId}`);
}
getCandidateWithSocialLinks(candidateId: number): Observable<ISideBar>{
if(this.candidateAndSocialLinks != null){
return of(this.candidateAndSocialLinks);
}
return this.http.get<ISideBar>(`/api/v1/cv/GetCandidateWithSocialLinks/${candidateId}`);
}
getResume(candidateId: number): Observable<IResume>{
if(this.resume != null){
return of(this.resume);
}
return this.http.get<IResume>(`/api/v1/cv/GetResume/${candidateId}`);
}
getProjects(candidateId: number): Observable<IProjects>{
if(this.projects != null){
return of(this.projects);
}
return this.http.get<ICv>(`/api/v1/cv/GetProjects/${candidateId}`);
}
getBlog(candidateId: number): Observable<IBlog>{
if(this.blog != null){
return of(this.blog);
}
return this.http.get<IBlog>(`/api/v1/cv/GetBlog/${candidateId}`);
}
sendMessage(contact: IContact, candidateId: number): Observable<boolean> {
return this.http.post<boolean>(`/api/v1/cv/SendMessage/${candidateId}`, contact);
}
}
+16
View File
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { LoaderService } from './loader.service';
describe('LoaderService', () => {
let service: LoaderService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(LoaderService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
+16
View File
@@ -0,0 +1,16 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class LoaderService {
private loading: boolean = false;
setLoading(loading: boolean){
this.loading = loading;
}
getLoading(): boolean{
return this.loading;
}
}
+3
View File
@@ -0,0 +1,3 @@
<div *ngIf="this.loader.getLoading()" class="cssload-container">
<div class="cssload-speeding-wheel"></div>
</div>
+65
View File
@@ -0,0 +1,65 @@
.cssload-container {
position: fixed;
width: 100%;
left: 0;
right: 0;
top: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.7);
z-index: 9999;
}
.cssload-speeding-wheel {
content: "";
display: block;
position: absolute;
left: 48%;
top: 40%;
width: 63px;
height: 63px;
margin: 0 auto;
border: 4px solid hsl(45, 54%, 58%);
border-radius: 50%;
border-left-color: transparent;
border-right-color: transparent;
animation: cssload-spin 1s infinite linear;
-o-animation: cssload-spin 1s infinite linear;
-ms-animation: cssload-spin 1s infinite linear;
-webkit-animation: cssload-spin 1s infinite linear;
-moz-animation: cssload-spin 1s infinite linear;
}
@keyframes cssload-spin {
100% {
transform: rotate(360deg);
transform: rotate(360deg);
}
}
@-o-keyframes cssload-spin {
100% {
-o-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@-ms-keyframes cssload-spin {
100% {
-ms-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@-webkit-keyframes cssload-spin {
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@-moz-keyframes cssload-spin {
100% {
-moz-transform: rotate(360deg);
transform: rotate(360deg);
}
}
+23
View File
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SpinnerComponent } from './spinner.component';
describe('SpinnerComponent', () => {
let component: SpinnerComponent;
let fixture: ComponentFixture<SpinnerComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [SpinnerComponent]
})
.compileComponents();
fixture = TestBed.createComponent(SpinnerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
+17
View File
@@ -0,0 +1,17 @@
import { Component, ViewEncapsulation } from '@angular/core';
import { LoaderService } from '../services/loader.service';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-spinner',
templateUrl: './spinner.component.html',
styleUrl: './spinner.component.scss',
encapsulation: ViewEncapsulation.ShadowDom,
standalone: true,
imports: [CommonModule]
})
export class SpinnerComponent {
constructor(public loader: LoaderService) {
}
}
+12
View File
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

+159
View File
@@ -0,0 +1,159 @@
'use strict';
// element toggle function
const elementToggleFunc = function (elem) { elem.classList.toggle("active"); }
// sidebar variables
const sidebar = document.querySelector("[data-sidebar]");
const sidebarBtn = document.querySelector("[data-sidebar-btn]");
// sidebar toggle functionality for mobile
sidebarBtn.addEventListener("click", function () { elementToggleFunc(sidebar); });
// testimonials variables
const testimonialsItem = document.querySelectorAll("[data-testimonials-item]");
const modalContainer = document.querySelector("[data-modal-container]");
const modalCloseBtn = document.querySelector("[data-modal-close-btn]");
const overlay = document.querySelector("[data-overlay]");
// modal variable
const modalImg = document.querySelector("[data-modal-img]");
const modalTitle = document.querySelector("[data-modal-title]");
const modalText = document.querySelector("[data-modal-text]");
// modal toggle function
const testimonialsModalFunc = function () {
modalContainer.classList.toggle("active");
overlay.classList.toggle("active");
}
// add click event to all modal items
for (let i = 0; i < testimonialsItem.length; i++) {
testimonialsItem[i].addEventListener("click", function () {
modalImg.src = this.querySelector("[data-testimonials-avatar]").src;
modalImg.alt = this.querySelector("[data-testimonials-avatar]").alt;
modalTitle.innerHTML = this.querySelector("[data-testimonials-title]").innerHTML;
modalText.innerHTML = this.querySelector("[data-testimonials-text]").innerHTML;
testimonialsModalFunc();
});
}
// add click event to modal close button
modalCloseBtn.addEventListener("click", testimonialsModalFunc);
overlay.addEventListener("click", testimonialsModalFunc);
// custom select variables
const select = document.querySelector("[data-select]");
const selectItems = document.querySelectorAll("[data-select-item]");
const selectValue = document.querySelector("[data-selecct-value]");
const filterBtn = document.querySelectorAll("[data-filter-btn]");
select.addEventListener("click", function () { elementToggleFunc(this); });
// add event in all select items
for (let i = 0; i < selectItems.length; i++) {
selectItems[i].addEventListener("click", function () {
let selectedValue = this.innerText.toLowerCase();
selectValue.innerText = this.innerText;
elementToggleFunc(select);
filterFunc(selectedValue);
});
}
// filter variables
const filterItems = document.querySelectorAll("[data-filter-item]");
const filterFunc = function (selectedValue) {
for (let i = 0; i < filterItems.length; i++) {
if (selectedValue === "all") {
filterItems[i].classList.add("active");
} else if (selectedValue === filterItems[i].dataset.category) {
filterItems[i].classList.add("active");
} else {
filterItems[i].classList.remove("active");
}
}
}
// add event in all filter button items for large screen
let lastClickedBtn = filterBtn[0];
for (let i = 0; i < filterBtn.length; i++) {
filterBtn[i].addEventListener("click", function () {
let selectedValue = this.innerText.toLowerCase();
selectValue.innerText = this.innerText;
filterFunc(selectedValue);
lastClickedBtn.classList.remove("active");
this.classList.add("active");
lastClickedBtn = this;
});
}
// contact form variables
const form = document.querySelector("[data-form]");
const formInputs = document.querySelectorAll("[data-form-input]");
const formBtn = document.querySelector("[data-form-btn]");
// add event to all form input field
for (let i = 0; i < formInputs.length; i++) {
formInputs[i].addEventListener("input", function () {
// check form validation
if (form.checkValidity()) {
formBtn.removeAttribute("disabled");
} else {
formBtn.setAttribute("disabled", "");
}
});
}
// page navigation variables
const navigationLinks = document.querySelectorAll("[data-nav-link]");
const pages = document.querySelectorAll("[data-page]");
// add event to all nav link
for (let i = 0; i < navigationLinks.length; i++) {
navigationLinks[i].addEventListener("click", function () {
for (let i = 0; i < pages.length; i++) {
if (this.innerHTML.toLowerCase() === pages[i].dataset.page) {
pages[i].classList.add("active");
navigationLinks[i].classList.add("active");
window.scrollTo(0, 0);
} else {
pages[i].classList.remove("active");
navigationLinks[i].classList.remove("active");
}
}
});
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,4 @@
export const environment = {
apiUrl: 'https://localhost:7013',
apiKey: "c6eAXYcNT873TT7BfMgQyS4ii7hxa53TLEUN7pAGaaU="
};
@@ -0,0 +1,4 @@
export const environment = {
apiUrl: 'https://localhost:7013',
apiKey: "c6eAXYcNT873TT7BfMgQyS4ii7hxa53TLEUN7pAGaaU="
};
+4
View File
@@ -0,0 +1,4 @@
export const environment = {
apiUrl: 'https://localhost:7013',
apiKey: "c6eAXYcNT873TT7BfMgQyS4ii7hxa53TLEUN7pAGaaU="
};
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 948 B

After

Width:  |  Height:  |  Size: 10 KiB

+39 -1
View File
@@ -2,12 +2,50 @@
<html lang="en">
<head>
<meta charset="utf-8">
<title>MyPortfolio</title>
<title>Bangara Raju Kottedi</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!--
- favicon
-->
<!-- <link rel="shortcut icon" href="./assets/images/logo.ico" type="image/x-icon"> -->
<!--
- custom css link
-->
<!-- <link rel="stylesheet" type="text/css" href="./assets/css/style.css"> -->
<!--
- google font link
-->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
<app-root></app-root>
<!--
- custom js link
-->
<!-- <script src="./assets/js/script.js"></script> -->
<!--
- ionicon link
-->
<!-- <script type="module" src="https://unpkg.com/ionicons@7.3.1/dist/ionicons/ionicons.esm.js"></script>
<script nomodule src="https://unpkg.com/ionicons@7.3.1/dist/ionicons/ionicons.js"></script> -->
<!--
- Font Awesome Pack
-->
<link rel="stylesheet" type="text/css" href="./assets/css/all.min.css">
<!-- <link href="https://cdn.jsdelivr.net/gh/eliyantosarage/font-awesome-pro@main/fontawesome-pro-6.5.2-web/css/all.min.css" rel="stylesheet"> -->
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More