Merge pull request #91 from HideyoshiSolutions/develop

develop - feat: implements a better kubernetes deployment setup
This commit is contained in:
2025-11-01 11:55:36 -03:00
committed by GitHub
51 changed files with 15415 additions and 17764 deletions

5
.dockerignore Normal file
View File

@@ -0,0 +1,5 @@
.github
.gitignore
Dockerfile
README.md
.k8s

108
.github/workflows/deploy.yml vendored Normal file
View File

@@ -0,0 +1,108 @@
name: ci
on:
push:
workflow_dispatch:
inputs:
tag:
description: 'Tag to deploy'
required: false
default: 'latest'
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [22.12.0]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: "npm"
- run: npm install
- run: npm run build --if-present
docker:
needs: [build]
runs-on: ubuntu-latest
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop')
permissions:
contents: read
packages: write # required to push to ghcr.io
id-token: write # optional for OIDC if you use it
steps:
- uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Prepare image tags
run: |
OWNER=$(echo "${GITHUB_REPOSITORY_OWNER}" | tr '[:upper:]' '[:lower:]')
REPO=$(echo "${GITHUB_REPOSITORY#*/}" | tr '[:upper:]' '[:lower:]')
# Determine tag
if [ "${GITHUB_REF_NAME}" = "main" ]; then
TAG="latest"
else
TAG="dev"
fi
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
IMAGE_BASE="ghcr.io/${OWNER}/${REPO}"
echo "IMAGE_LATEST=${IMAGE_BASE}:${TAG}" >> $GITHUB_ENV
echo "IMAGE_SHA=${IMAGE_BASE}:sha-${SHORT_SHA}" >> $GITHUB_ENV
- name: Build Docker image
run: |
docker build -t $IMAGE_LATEST -t $IMAGE_SHA .
- name: Push Docker images
run: |
docker push $IMAGE_LATEST
docker push $IMAGE_SHA
deploy:
needs: [docker]
runs-on: ubuntu-latest
if: github.event_name == 'push' && (github.ref == 'refs/heads/main')
steps:
- uses: actions/checkout@v4
- name: Set up Kubeconfig
uses: azure/setup-kubectl@v3
with:
method: kubeconfig
kubeconfig: ${{ secrets.PORTFOLIO_KUBECONFIG }}
- name: Deploy to Kubernetes
run: |
OWNER=$(echo "${GITHUB_REPOSITORY_OWNER}" | tr '[:upper:]' '[:lower:]')
REPO=$(echo "${GITHUB_REPOSITORY#*/}" | tr '[:upper:]' '[:lower:]')
IMAGE_BASE="ghcr.io/${OWNER}/${REPO}"
TAG="${{ github.event.inputs.tag || 'latest' }}"
kubectl config set-context --current --namespace=$KUBE_NAMESPACE
# Apply any other configuration changes if needed
envsubst < .k8s/config.yml | kubectl apply -f -
envsubst < .k8s/deployment.yaml | kubectl apply -f -
envsubst < .k8s/service.yaml | kubectl apply -f -
envsubst < .k8s/ingress.yaml | kubectl apply -f -

View File

@@ -1,61 +0,0 @@
name: ci
on:
push:
branches:
- "main"
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: "npm"
- run: npm install
- run: npm run build --if-present
docker:
needs: [build]
runs-on: ubuntu-latest
steps:
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v2
with:
platforms: linux/amd64,linux/arm64
push: true
tags: yoshiunfriendly/frontend-hideyoshi.com:latest
run-dispatcher:
needs: docker
runs-on: ubuntu-latest
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- name: Runs Infra-Hideyoshi.com Deployment Dispatcher
run: |
curl -X POST https://api.github.com/repos/HideyoshiSolutions/infra-hideyoshi.com/dispatches \
-H 'Accept: application/vnd.github.everest-preview+json' \
-u ${{ secrets.ACTIONS_KEY }} \
--data '{"event_type": "refresh-deployments", "client_payload": { "deployments": "frontend-deployment" }}'

View File

@@ -1,27 +0,0 @@
name: vercel-cleanup-pr
on:
pull_request:
types: [closed]
env:
VERCEL_CLI_TOKEN: ${{ secrets.VERCEL_CLI_TOKEN }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
GITHUB_PR_ID: ${{ github.event.number }}
jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- name: Cleanup Vercel Deployments
run: |
closed_deployments=$(curl "https://api.vercel.com/v6/deployments?projectId=$VERCEL_PROJECT_ID" \
-H "Accept: application/json" \
-H "Authorization: Bearer ${VERCEL_CLI_TOKEN}" | jq -r ".deployments[] | select(.meta.githubPrId == \"${GITHUB_PR_ID}\") | .uid")
for deployment in $closed_deployments; do
echo "Deleting Deployment: $deployment"
curl "https://api.vercel.com/v6/now/deployments/$deployment" \
-X DELETE \
-H "Authorization: Bearer ${VERCEL_CLI_TOKEN}"
done

View File

@@ -1,33 +0,0 @@
name: vercel-cleanup-preview
on:
push:
branches:
- '*'
- '!main'
- '!devel'
env:
VERCEL_CLI_TOKEN: ${{ secrets.VERCEL_CLI_TOKEN }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
GIT_PREVIOS_COMMIT: ${{ github.event.before }}
jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Cleanup Vercel Deployments
run: |
invalid_deployments=$(curl "https://api.vercel.com/v6/deployments?projectId=$VERCEL_PROJECT_ID" \
-H "Accept: application/json" \
-H "Authorization: Bearer ${VERCEL_CLI_TOKEN}" | jq -r ".deployments[] | select(.meta.githubCommitSha == \"${GIT_PREVIOS_COMMIT}\") | .uid")
for deployment in $invalid_deployments; do
echo "Deleting Deployment: $deployment"
curl "https://api.vercel.com/v6/now/deployments/$deployment" \
-X DELETE \
-H "Authorization: Bearer ${VERCEL_CLI_TOKEN}"
done

8
.k8s/config.yml Normal file
View File

@@ -0,0 +1,8 @@
apiVersion: v1
kind: ConfigMap
metadata:
namespace: ${KUBE_NAMESPACE}
name: frontend-config
data:
BACKEND_URL: "${BACKEND_URL}"
GITHUB_USER: "${GH_USER}"

59
.k8s/deployment.yaml Normal file
View File

@@ -0,0 +1,59 @@
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: ${KUBE_NAMESPACE}
name: frontend-deployment
labels:
app: frontend
spec:
replicas: 1
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
nodeSelector:
${WORKER_NODE_LABEL}
initContainers:
- name: wait-backend-init
image: busybox:latest
args:
- /bin/sh
- -c
- >
set -x;
while [ $(curl -sw '%{http_code}' "backend-service:8000/health" -o /dev/null) -ne 200 ]; do
sleep 15;
done
containers:
- name: frontend
image: ${IMAGE_BASE}:${TAG}
imagePullPolicy: "Always"
resources:
requests:
memory: "128Mi"
cpu: "75m"
limits:
memory: "128Mi"
cpu: "256m"
ports:
- containerPort: 5000
readinessProbe:
httpGet:
path: /
port: 5000
initialDelaySeconds: 10
livenessProbe:
httpGet:
path: /
port: 5000
initialDelaySeconds: 10
envFrom:
- configMapRef:
name: frontend-config
env:
- name: PORT
value: "5000"

25
.k8s/ingress.yaml Normal file
View File

@@ -0,0 +1,25 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
namespace: ${KUBE_NAMESPACE}
name: nginx-ingress
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/use-regex: "true"
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
tls:
- hosts:
- ${KUBE_DOMAIN}
secretName: letsencrypt-cluster-certificate-tls
rules:
- host: ${KUBE_DOMAIN}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 5000

13
.k8s/service.yaml Normal file
View File

@@ -0,0 +1,13 @@
apiVersion: v1
kind: Service
metadata:
namespace: ${KUBE_NAMESPACE}
name: frontend-service
spec:
selector:
app: frontend
ports:
- port: 5000
protocol: TCP
targetPort: 5000
type: ClusterIP

View File

@@ -1,16 +1,30 @@
FROM node:18-alpine FROM node:22.12-alpine AS base
FROM base AS build
WORKDIR /app WORKDIR /app
COPY package*.json ./
RUN npm install
FROM base AS prod
WORKDIR /app
COPY --from=build /app/node_modules ./node_modules
COPY . . COPY . .
RUN npm install -g @angular/cli@20.3.8
RUN apk add --update gettext python3 py3-pip py3-setuptools make g++ && \ RUN apk add --update gettext python3 py3-pip py3-setuptools make g++ && \
rm -rf /var/cache/apk/* rm -rf /var/cache/apk/*
RUN npm install
RUN npm install -g @angular/cli@16
RUN npm run build:prod RUN npm run build:prod
EXPOSE 5000-7000 EXPOSE 5000-7000
CMD ["npm", "run", "start:prod"] CMD ["npm", "run", "start:prod"]

View File

@@ -11,12 +11,15 @@
"prefix": "app", "prefix": "app",
"architect": { "architect": {
"build": { "build": {
"builder": "@angular-devkit/build-angular:browser", "builder": "@angular/build:application",
"options": { "options": {
"outputPath": "dist/frontend-hideyoshi.com", "outputPath": {
"base": "dist/frontend-hideyoshi.com"
},
"index": "src/index.html", "index": "src/index.html",
"main": "src/main.ts", "polyfills": [
"polyfills": "src/polyfills.ts", "src/polyfills.ts"
],
"tsConfig": "tsconfig.app.json", "tsConfig": "tsconfig.app.json",
"allowedCommonJsDependencies": [ "allowedCommonJsDependencies": [
"ts-interface-checker", "ts-interface-checker",
@@ -39,21 +42,21 @@
"node_modules/bootstrap/dist/js/bootstrap.bundle.js", "node_modules/bootstrap/dist/js/bootstrap.bundle.js",
"node_modules/cookieconsent/build/cookieconsent.min.js" "node_modules/cookieconsent/build/cookieconsent.min.js"
], ],
"serviceWorker": true, "serviceWorker": "ngsw-config.json",
"ngswConfigPath": "ngsw-config.json" "browser": "src/main.ts"
}, },
"configurations": { "configurations": {
"production": { "production": {
"budgets": [ "budgets": [
{ {
"type": "initial", "type": "initial",
"maximumWarning": "1mb", "maximumWarning": "2.5mb",
"maximumError": "1.5mb" "maximumError": "5mb"
}, },
{ {
"type": "anyComponentStyle", "type": "anyComponentStyle",
"maximumWarning": "2.5kb", "maximumWarning": "5kb",
"maximumError": "5kb" "maximumError": "10kb"
} }
], ],
"fileReplacements": [ "fileReplacements": [
@@ -63,12 +66,10 @@
} }
], ],
"outputHashing": "all", "outputHashing": "all",
"serviceWorker": true "serviceWorker": "ngsw-config.json"
}, },
"development": { "development": {
"buildOptimizer": false,
"optimization": false, "optimization": false,
"vendorChunk": true,
"extractLicenses": false, "extractLicenses": false,
"sourceMap": true, "sourceMap": true,
"namedChunks": true "namedChunks": true
@@ -77,25 +78,25 @@
"defaultConfiguration": "production" "defaultConfiguration": "production"
}, },
"serve": { "serve": {
"builder": "@angular-devkit/build-angular:dev-server", "builder": "@angular/build:dev-server",
"configurations": { "configurations": {
"production": { "production": {
"browserTarget": "frontend-hideyoshi.com:build:production" "buildTarget": "frontend-hideyoshi.com:build:production"
}, },
"development": { "development": {
"browserTarget": "frontend-hideyoshi.com:build:development" "buildTarget": "frontend-hideyoshi.com:build:development"
} }
}, },
"defaultConfiguration": "development" "defaultConfiguration": "development"
}, },
"extract-i18n": { "extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n", "builder": "@angular/build:extract-i18n",
"options": { "options": {
"browserTarget": "frontend-hideyoshi.com:build" "buildTarget": "frontend-hideyoshi.com:build"
} }
}, },
"test": { "test": {
"builder": "@angular-devkit/build-angular:karma", "builder": "@angular/build:application",
"options": { "options": {
"main": "src/test.ts", "main": "src/test.ts",
"polyfills": "src/polyfills.ts", "polyfills": "src/polyfills.ts",
@@ -129,6 +130,30 @@
}, },
"@angular-eslint/schematics:library": { "@angular-eslint/schematics:library": {
"setParserOptionsProject": true "setParserOptionsProject": true
},
"@schematics/angular:component": {
"type": "component"
},
"@schematics/angular:directive": {
"type": "directive"
},
"@schematics/angular:service": {
"type": "service"
},
"@schematics/angular:guard": {
"typeSeparator": "."
},
"@schematics/angular:interceptor": {
"typeSeparator": "."
},
"@schematics/angular:module": {
"typeSeparator": "."
},
"@schematics/angular:pipe": {
"typeSeparator": "."
},
"@schematics/angular:resolver": {
"typeSeparator": "."
} }
} }
} }

16831
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,33 +1,27 @@
{ {
"name": "frontend-hideyoshi.com", "name": "frontend-hideyoshi.com",
"version": "0.0.0",
"scripts": {
"start": "node ./set_env.js && node ./server.js",
"serve": "node ./set_env.js && ng serve",
"build": "ng build",
"start:prod": "node ./set_env.js --prod && node ./server.js",
"serve:prod": "node ./set_env.js --prod && ng serve --configuration=production",
"build:prod": "ng build --configuration=production"
},
"proxy": {
"/callback": {
"target": "http://localhost:8070"
}
},
"private": true, "private": true,
"scripts": {
"build": "ng build",
"build:prod": "ng build --configuration=production",
"serve": "node ./set_env.js && ng serve",
"serve:prod": "node ./set_env.js --prod && ng serve --configuration=production",
"start": "node ./set_env.js && node ./server.js",
"start:prod": "node ./set_env.js --prod && node ./server.js"
},
"dependencies": { "dependencies": {
"@angular/animations": "^16.2.2", "@angular/animations": "^20.3.9",
"@angular/cdk": "^16.2.1", "@angular/cdk": "^19.2.19",
"@angular/common": "^16.2.2", "@angular/common": "^20.3.9",
"@angular/compiler": "^16.2.2", "@angular/compiler": "^20.3.9",
"@angular/core": "^16.2.2", "@angular/core": "^20.3.9",
"@angular/forms": "^16.2.2", "@angular/forms": "^20.3.9",
"@angular/material": "^16.2.1", "@angular/material": "^19.2.19",
"@angular/platform-browser": "^16.2.2", "@angular/platform-browser": "^20.3.9",
"@angular/platform-browser-dynamic": "^16.2.2", "@angular/platform-browser-dynamic": "^20.3.9",
"@angular/router": "^16.2.2", "@angular/router": "^20.3.9",
"@angular/service-worker": "^16.2.2", "@angular/service-worker": "^20.3.9",
"@fortawesome/angular-fontawesome": "^0.13.0", "@fortawesome/angular-fontawesome": "^3.0.0",
"@fortawesome/fontawesome-svg-core": "^6.1.1", "@fortawesome/fontawesome-svg-core": "^6.1.1",
"@fortawesome/free-brands-svg-icons": "^6.1.1", "@fortawesome/free-brands-svg-icons": "^6.1.1",
"@fortawesome/free-regular-svg-icons": "^6.1.1", "@fortawesome/free-regular-svg-icons": "^6.1.1",
@@ -38,37 +32,38 @@
"bootstrap": "^4.6.2", "bootstrap": "^4.6.2",
"cookieconsent": "^3.1.1", "cookieconsent": "^3.1.1",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^17.2.3",
"envsub": "^4.1.0", "envsub": "^4.1.0",
"express": "^4.18.1", "express": "^4.18.1",
"jquery": "^3.6.0", "jquery": "^3.6.0",
"ng-apexcharts": "^1.8.0", "ng-apexcharts": "^1.8.0",
"ngx-cookie-service": "^16.0.1", "ngx-cookie-service": "^20.1.1",
"ngx-cookieconsent": "^4.0.2", "ngx-cookieconsent": "^4.0.2",
"ngx-glide": "^16.0.0", "ngx-glide": "^16.0.0",
"normalize.css": "^8.0.1", "normalize.css": "^8.0.1",
"rxjs": "~7.5.0", "rxjs": "~7.5.0",
"ts-interface-checker": "^1.0.2", "ts-interface-checker": "^1.0.2",
"tslib": "^2.3.0", "tslib": "^2.3.0",
"zone.js": "~0.13.1" "zone.js": "~0.15.1"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "^16.2.0", "@angular-eslint/builder": "^19.8.1",
"@angular-eslint/builder": "^16.1.1", "@angular-eslint/eslint-plugin": "^20.5.0",
"@angular-eslint/eslint-plugin": "16.1.1", "@angular-eslint/eslint-plugin-template": "^20.5.0",
"@angular-eslint/eslint-plugin-template": "16.1.1", "@angular-eslint/schematics": "^20.5.0",
"@angular-eslint/schematics": "16.1.1", "@angular-eslint/template-parser": "^20.5.0",
"@angular-eslint/template-parser": "16.1.1", "@angular/build": "^20.3.8",
"@angular/cli": "^16.2.0", "@angular/cli": "^20.3.8",
"@angular/compiler-cli": "^16.2.2", "@angular/compiler-cli": "^20.3.9",
"@types/jasmine": "~4.0.0", "@types/jasmine": "~4.0.0",
"@types/node": "^18.11.19", "@types/node": "^22.18.13",
"@typescript-eslint/eslint-plugin": "^5.59.2", "@typescript-eslint/eslint-plugin": "^8.46.2",
"@typescript-eslint/parser": "^5.59.2", "@typescript-eslint/parser": "^8.46.2",
"eslint": "^8.39.0", "eslint": "^8.39.0",
"eslint-config-prettier": "^9.0.0", "eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.1", "eslint-plugin-prettier": "^5.0.1",
"jasmine-core": "~4.1.0", "jasmine-core": "~4.1.0",
"karma": "~6.3.0", "karma": "~6.4.4",
"karma-chrome-launcher": "~3.1.0", "karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.2.0", "karma-coverage": "~2.2.0",
"karma-jasmine": "~5.0.0", "karma-jasmine": "~5.0.0",
@@ -76,6 +71,11 @@
"prettier": "^3.0.3", "prettier": "^3.0.3",
"prettier-eslint": "^16.1.1", "prettier-eslint": "^16.1.1",
"ts-interface-builder": "^0.3.3", "ts-interface-builder": "^0.3.3",
"typescript": "~4.9.5" "typescript": "~5.8.3"
},
"proxy": {
"/callback": {
"target": "http://localhost:8070"
}
} }
} }

View File

@@ -9,6 +9,7 @@ import {CookieConsertService} from './shared/cookie-consent/cookie-consert.servi
selector: 'app-root', selector: 'app-root',
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrls: ['./app.component.css'], styleUrls: ['./app.component.css'],
standalone: false
}) })
export class AppComponent implements OnInit { export class AppComponent implements OnInit {
title = 'frontend-hideyoshi.com'; title = 'frontend-hideyoshi.com';

View File

@@ -5,6 +5,7 @@ import {faGithub, faLinkedinIn, faTwitter,} from '@fortawesome/free-brands-svg-i
selector: 'app-footer', selector: 'app-footer',
templateUrl: './footer.component.html', templateUrl: './footer.component.html',
styleUrls: ['./footer.component.css'], styleUrls: ['./footer.component.css'],
standalone: false
}) })
export class FooterComponent { export class FooterComponent {
_githubIcon = faGithub; _githubIcon = faGithub;

View File

@@ -9,28 +9,36 @@
(@dropdownState.done)=" (@dropdownState.done)="
$event.element.style.display = state ? 'block' : 'none' $event.element.style.display = state ? 'block' : 'none'
" "
> >
<div class="info"> <div class="info">
<h3>{{ this.user ? this.user.username : "User Account" }}</h3> <h3>{{ this.user ? this.user.username : "User Account" }}</h3>
</div> </div>
<div #management> <div #management>
<ul class="user-management" *ngIf="!this.user"> @if (!this.user) {
<li *ngFor="let option of mainOptions" <ul class="user-management">
@for (option of mainOptions; track option) {
<li
class="dropdown-item" (click)=option.callback()> class="dropdown-item" (click)=option.callback()>
<div class="icon-box"> <div class="icon-box">
<fa-icon [icon]="option.icon"></fa-icon> <fa-icon [icon]="option.icon"></fa-icon>
</div> </div>
<p>{{option.text}}</p> <p>{{option.text}}</p>
</li> </li>
}
</ul> </ul>
<ul class="user-management" *ngIf="this.user"> }
<li *ngFor="let option of userOptions" @if (this.user) {
<ul class="user-management">
@for (option of userOptions; track option) {
<li
class="dropdown-item" (click)=option.callback()> class="dropdown-item" (click)=option.callback()>
<div class="icon-box"> <div class="icon-box">
<fa-icon [icon]="option.icon"></fa-icon> <fa-icon [icon]="option.icon"></fa-icon>
</div> </div>
<p>{{option.text}}</p> <p>{{option.text}}</p>
</li> </li>
}
</ul> </ul>
}
</div> </div>
</div> </div>

View File

@@ -13,22 +13,17 @@ import {Value} from "@sinclair/typebox/value";
styleUrls: ['./header-dropdown.component.css'], styleUrls: ['./header-dropdown.component.css'],
animations: [ animations: [
trigger('dropdownState', [ trigger('dropdownState', [
state( state('hide', style({
'hide',
style({
opacity: '0', opacity: '0',
}), })),
), state('show', style({
state(
'show',
style({
opacity: '1', opacity: '1',
}), })),
),
transition('hide => show', animate('20ms ease-in')), transition('hide => show', animate('20ms ease-in')),
transition('show => hide', animate('5ms ease-out')), transition('show => hide', animate('5ms ease-out')),
]), ]),
], ],
standalone: false
}) })
export class HeaderDropdownComponent implements OnInit, OnDestroy { export class HeaderDropdownComponent implements OnInit, OnDestroy {
mainOptions: { text: string, icon: IconDefinition, callback: () => void }[] = [ mainOptions: { text: string, icon: IconDefinition, callback: () => void }[] = [

View File

@@ -6,6 +6,7 @@ import {AuthService} from 'src/app/shared/service/auth.service';
selector: 'app-callback', selector: 'app-callback',
templateUrl: './callback.component.html', templateUrl: './callback.component.html',
styleUrls: ['./callback.component.css'], styleUrls: ['./callback.component.css'],
standalone: false
}) })
export class CallbackComponent implements OnInit { export class CallbackComponent implements OnInit {
constructor( constructor(

View File

@@ -1,3 +1,5 @@
<div class="error-box" *ngIf="errorMessage"> @if (errorMessage) {
<div class="error-box">
{{ errorMessage }} {{ errorMessage }}
</div> </div>
}

View File

@@ -4,6 +4,7 @@ import {Component, Input} from '@angular/core';
selector: 'app-error-box', selector: 'app-error-box',
templateUrl: './error-box.component.html', templateUrl: './error-box.component.html',
styleUrls: ['./error-box.component.css'], styleUrls: ['./error-box.component.css'],
standalone: false
}) })
export class ErrorBoxComponent { export class ErrorBoxComponent {
@Input() @Input()

View File

@@ -4,6 +4,7 @@ import {Component, EventEmitter, Input, Output} from '@angular/core';
selector: 'app-help', selector: 'app-help',
templateUrl: './help.component.html', templateUrl: './help.component.html',
styleUrls: ['./help.component.css'], styleUrls: ['./help.component.css'],
standalone: false
}) })
export class HelpComponent { export class HelpComponent {
@Input() @Input()

View File

@@ -2,7 +2,7 @@
[state]="state" [state]="state"
(stateChange)="onStateChange($event)" (stateChange)="onStateChange($event)"
[ignoreClickOutside]="ignoreClickOutside" [ignoreClickOutside]="ignoreClickOutside"
> >
<div <div
class="container m-0 overflow-hidden" class="container m-0 overflow-hidden"
[@resizeContainerForErrorMessage]="hideErrorMessage()" [@resizeContainerForErrorMessage]="hideErrorMessage()"
@@ -65,16 +65,18 @@
[disabled]="isCookieBlocked" [disabled]="isCookieBlocked"
(click)="onGoogleLogin()" (click)="onGoogleLogin()"
> >
@if (!isCookieBlocked) {
<mat-icon <mat-icon
*ngIf="!isCookieBlocked"
style="width: 50px; height: 30px" style="width: 50px; height: 30px"
svgIcon="google-logo" svgIcon="google-logo"
></mat-icon> ></mat-icon>
}
@if (isCookieBlocked) {
<mat-icon <mat-icon
*ngIf="isCookieBlocked"
style="width: 50px; height: 30px" style="width: 50px; height: 30px"
svgIcon="google-disabled-logo" svgIcon="google-disabled-logo"
></mat-icon> ></mat-icon>
}
Login With Google Login With Google
</button> </button>
<button <button

View File

@@ -33,56 +33,39 @@ const GITHUB_LOGO_SVG = 'assets/img/providers/github.svg';
styleUrls: ['./login.component.css'], styleUrls: ['./login.component.css'],
animations: [ animations: [
trigger('resizeContainerForErrorMessage', [ trigger('resizeContainerForErrorMessage', [
state( state('hide', style({
'hide',
style({
height: '100px', height: '100px',
width: '320px', width: '320px',
}), })),
), transition('show => hide', group([
transition(
'show => hide',
group([
query('@*', animateChild(), { optional: true }), query('@*', animateChild(), { optional: true }),
animate('1s ease'), animate('1s ease'),
]), ])),
),
]), ]),
trigger('showErrorMessage', [ trigger('showErrorMessage', [
state( state('show', style({
'show',
style({
opacity: 1, opacity: 1,
height: '100px', height: '100px',
width: '320px', width: '320px',
}), })),
), state('hide', style({
state(
'hide',
style({
opacity: 0, opacity: 0,
height: '0px', height: '0px',
width: '0px', width: '0px',
}), })),
),
transition('* => show', animate('500ms ease-in')), transition('* => show', animate('500ms ease-in')),
]), ]),
trigger('hideAuthContainer', [ trigger('hideAuthContainer', [
state( state('hide', style({
'hide',
style({
opacity: 0, opacity: 0,
}), })),
), transition('show => hide', group([
transition(
'show => hide',
group([
query('@*', animateChild(), { optional: true }), query('@*', animateChild(), { optional: true }),
animate('250ms ease-out'), animate('250ms ease-out'),
]), ])),
),
]), ]),
], ],
standalone: false
}) })
export class LoginComponent implements OnInit, AfterViewInit, OnDestroy { export class LoginComponent implements OnInit, AfterViewInit, OnDestroy {
@Input() @Input()

View File

@@ -13,56 +13,39 @@ import {faFileUpload} from '@fortawesome/free-solid-svg-icons';
styleUrls: ['./my-profile.component.css'], styleUrls: ['./my-profile.component.css'],
animations: [ animations: [
trigger('resizeContainerForErrorMessage', [ trigger('resizeContainerForErrorMessage', [
state( state('hide', style({
'hide',
style({
height: '100px', height: '100px',
width: '320px', width: '320px',
}), })),
), transition('show => hide', group([
transition(
'show => hide',
group([
query('@*', animateChild(), { optional: true }), query('@*', animateChild(), { optional: true }),
animate('1s ease'), animate('1s ease'),
]), ])),
),
]), ]),
trigger('showErrorMessage', [ trigger('showErrorMessage', [
state( state('show', style({
'show',
style({
opacity: 1, opacity: 1,
height: '100px', height: '100px',
width: '320px', width: '320px',
}), })),
), state('hide', style({
state(
'hide',
style({
opacity: 0, opacity: 0,
height: '0px', height: '0px',
width: '0px', width: '0px',
}), })),
),
transition('* => show', animate('500ms ease-in')), transition('* => show', animate('500ms ease-in')),
]), ]),
trigger('hideAuthContainer', [ trigger('hideAuthContainer', [
state( state('hide', style({
'hide',
style({
opacity: 0, opacity: 0,
}), })),
), transition('show => hide', group([
transition(
'show => hide',
group([
query('@*', animateChild(), { optional: true }), query('@*', animateChild(), { optional: true }),
animate('250ms ease-out'), animate('250ms ease-out'),
]), ])),
),
]), ]),
], ],
standalone: false
}) })
export class MyProfileComponent implements OnInit { export class MyProfileComponent implements OnInit {
@Input() @Input()

View File

@@ -5,6 +5,7 @@ import {AuthService} from '../../../../shared/service/auth.service';
selector: 'app-profile-picture-picker', selector: 'app-profile-picture-picker',
templateUrl: './profile-picture-picker.component.html', templateUrl: './profile-picture-picker.component.html',
styleUrls: ['./profile-picture-picker.component.css'], styleUrls: ['./profile-picture-picker.component.css'],
standalone: false
}) })
export class ProfilePicturePickerComponent { export class ProfilePicturePickerComponent {
@Output() @Output()

View File

@@ -22,56 +22,39 @@ const GITHUB_LOGO_SVG = 'assets/img/providers/github.svg';
styleUrls: ['./signup.component.css'], styleUrls: ['./signup.component.css'],
animations: [ animations: [
trigger('resizeContainerForErrorMessage', [ trigger('resizeContainerForErrorMessage', [
state( state('hide', style({
'hide',
style({
height: '100px', height: '100px',
width: '320px', width: '320px',
}), })),
), transition('show => hide', group([
transition(
'show => hide',
group([
query('@*', animateChild(), { optional: true }), query('@*', animateChild(), { optional: true }),
animate('1s ease'), animate('1s ease'),
]), ])),
),
]), ]),
trigger('showErrorMessage', [ trigger('showErrorMessage', [
state( state('show', style({
'show',
style({
opacity: 1, opacity: 1,
height: '100px', height: '100px',
width: '320px', width: '320px',
}), })),
), state('hide', style({
state(
'hide',
style({
opacity: 0, opacity: 0,
height: '0px', height: '0px',
width: '0px', width: '0px',
}), })),
),
transition('* => show', animate('500ms ease-in')), transition('* => show', animate('500ms ease-in')),
]), ]),
trigger('hideAuthContainer', [ trigger('hideAuthContainer', [
state( state('hide', style({
'hide',
style({
opacity: 0, opacity: 0,
}), })),
), transition('show => hide', group([
transition(
'show => hide',
group([
query('@*', animateChild(), { optional: true }), query('@*', animateChild(), { optional: true }),
animate('250ms ease-out'), animate('250ms ease-out'),
]), ])),
),
]), ]),
], ],
standalone: false
}) })
export class SignupComponent implements OnInit { export class SignupComponent implements OnInit {
@Input() @Input()

View File

@@ -7,18 +7,12 @@ import {Component, EventEmitter, Input, Output} from '@angular/core';
styleUrls: ['./header-slider.component.css'], styleUrls: ['./header-slider.component.css'],
animations: [ animations: [
trigger('slideState', [ trigger('slideState', [
state( state('hide', style({
'hide',
style({
transform: 'translateX(100%)', transform: 'translateX(100%)',
}), })),
), state('show', style({
state(
'show',
style({
transform: 'translateX(0%)', transform: 'translateX(0%)',
}), })),
),
transition('hide => show', [ transition('hide => show', [
group([ group([
query('@*', animateChild(), { optional: true }), query('@*', animateChild(), { optional: true }),
@@ -33,6 +27,7 @@ import {Component, EventEmitter, Input, Output} from '@angular/core';
]), ]),
]), ]),
], ],
standalone: false
}) })
export class HeaderSliderComponent { export class HeaderSliderComponent {
@Input() @Input()

View File

@@ -1,8 +1,8 @@
<div class="links-container"> <div class="links-container">
<div class="nav-links"> <div class="nav-links">
<ul> <ul>
@for (page of pages; track page; let i = $index) {
<li <li
*ngFor="let page of pages; let i = index"
[@animateSliderItem]="{ [@animateSliderItem]="{
value: itemStatus, value: itemStatus,
params: { params: {
@@ -15,8 +15,9 @@
{{ page.name }} {{ page.name }}
</a> </a>
</li> </li>
}
</ul> </ul>
</div> </div>
</div> </div>
<div class="profile-container"> <div class="profile-container">
@@ -32,13 +33,14 @@
#profile #profile
> >
<div class="profile-btn" (click)="onProfileButtonClicked()"> <div class="profile-btn" (click)="onProfileButtonClicked()">
@if (!loggedUser || !loggedUser.profilePictureUrl) {
<fa-icon <fa-icon
*ngIf="!loggedUser || !loggedUser.profilePictureUrl"
class="fas fa-user" class="fas fa-user"
[icon]="userIcon" [icon]="userIcon"
></fa-icon> ></fa-icon>
}
@if (!!loggedUser && !!loggedUser.profilePictureUrl) {
<img <img
*ngIf="!!loggedUser && !!loggedUser.profilePictureUrl"
class="profile-picture" class="profile-picture"
[ngSrc]="loggedUser.profilePictureUrl" [ngSrc]="loggedUser.profilePictureUrl"
width="50" width="50"
@@ -46,6 +48,7 @@
alt="Profile Picture" alt="Profile Picture"
priority priority
/> />
</div> }
</div> </div>
</div> </div>
</div>

View File

@@ -10,6 +10,7 @@ import {Value} from "@sinclair/typebox/value";
selector: 'app-nav-slider', selector: 'app-nav-slider',
templateUrl: './nav-slider.component.html', templateUrl: './nav-slider.component.html',
styleUrls: ['./nav-slider.component.css'], styleUrls: ['./nav-slider.component.css'],
standalone: false
}) })
export class NavSliderComponent export class NavSliderComponent
extends SliderItemComponent extends SliderItemComponent

View File

@@ -1,11 +1,10 @@
<div class="user-container"> <div class="user-container">
<div class="user-options"> <div class="user-options">
<ul> <ul>
@for (
options of user ? userOptions : userlessOptions; track
options; let i = $index) {
<li <li
*ngFor="
let options of user ? userOptions : userlessOptions;
let i = index
"
[@animateSliderItem]="{ [@animateSliderItem]="{
value: itemStatus, value: itemStatus,
params: { params: {
@@ -18,6 +17,7 @@
{{ options.name }} {{ options.name }}
</a> </a>
</li> </li>
}
</ul> </ul>
</div> </div>
</div> </div>

View File

@@ -9,6 +9,7 @@ import {Value} from "@sinclair/typebox/value";
selector: 'app-user-slider', selector: 'app-user-slider',
templateUrl: './user-slider.component.html', templateUrl: './user-slider.component.html',
styleUrls: ['./user-slider.component.css'], styleUrls: ['./user-slider.component.css'],
standalone: false
}) })
export class UserSliderComponent extends SliderItemComponent implements OnInit { export class UserSliderComponent extends SliderItemComponent implements OnInit {
userlessOptions = [ userlessOptions = [

View File

@@ -7,9 +7,11 @@
</div> </div>
<div class="nav-links"> <div class="nav-links">
<ul class="link-container"> <ul class="link-container">
<li *ngFor="let page of pages"> @for (page of pages; track page) {
<li>
<a [routerLink]="page.route">{{ page.name }}</a> <a [routerLink]="page.route">{{ page.name }}</a>
</li> </li>
}
</ul> </ul>
</div> </div>
@@ -19,13 +21,14 @@
(click)="toogleProfileDropdown()" (click)="toogleProfileDropdown()"
#profileBtn #profileBtn
> >
@if (!loggedUser || !loggedUser.profilePictureUrl) {
<fa-icon <fa-icon
*ngIf="!loggedUser || !loggedUser.profilePictureUrl"
class="fas fa-user" class="fas fa-user"
[icon]="userIcon" [icon]="userIcon"
></fa-icon> ></fa-icon>
}
@if (!!loggedUser && !!loggedUser.profilePictureUrl) {
<img <img
*ngIf="!!loggedUser && !!loggedUser.profilePictureUrl"
class="profile-picture" class="profile-picture"
[ngSrc]="loggedUser.profilePictureUrl" [ngSrc]="loggedUser.profilePictureUrl"
width="50" width="50"
@@ -33,6 +36,7 @@
alt="Profile Picture" alt="Profile Picture"
priority priority
/> />
}
</div> </div>
<app-header-dropdown <app-header-dropdown

View File

@@ -13,6 +13,7 @@ import {Value} from "@sinclair/typebox/value";
selector: 'app-header', selector: 'app-header',
templateUrl: './header.component.html', templateUrl: './header.component.html',
styleUrls: ['./header.component.css'], styleUrls: ['./header.component.css'],
standalone: false
}) })
export class HeaderComponent implements OnInit, OnDestroy { export class HeaderComponent implements OnInit, OnDestroy {
pages: { name: string; route: string }[] = [ pages: { name: string; route: string }[] = [

View File

@@ -4,6 +4,7 @@ import {Component} from '@angular/core';
selector: 'app-home', selector: 'app-home',
templateUrl: './home.component.html', templateUrl: './home.component.html',
styleUrls: ['./home.component.css'], styleUrls: ['./home.component.css'],
standalone: false
}) })
export class HomeComponent { export class HomeComponent {
constructor() {} constructor() {}

View File

@@ -23,6 +23,7 @@ import {animate, state, style, transition, trigger} from "@angular/animations";
]), ]),
]) ])
], ],
standalone: false
}) })
export class StackCardComponent { export class StackCardComponent {
@Input() @Input()

View File

@@ -1,5 +1,9 @@
<ngx-glide #ngxGlide class="container stack-slider" *ngIf="stacks && stacks.length > 0"> @if (stacks && stacks.length > 0) {
<app-stack-card *ngFor="let stack of stacks" class="slider-card" [stack]="stack" <ngx-glide #ngxGlide class="container stack-slider">
@for (stack of stacks; track stack) {
<app-stack-card class="slider-card" [stack]="stack"
[inFocus]="isInFocus(stack)"> [inFocus]="isInFocus(stack)">
</app-stack-card> </app-stack-card>
</ngx-glide> }
</ngx-glide>
}

View File

@@ -6,7 +6,8 @@ import {Stack} from "../../shared/model/stack/stack.model";
@Component({ @Component({
selector: 'app-stack-slider', selector: 'app-stack-slider',
templateUrl: './stack-slider.component.html', templateUrl: './stack-slider.component.html',
styleUrls: ['./stack-slider.component.css'] styleUrls: ['./stack-slider.component.css'],
standalone: false
}) })
export class StackSliderComponent implements AfterViewInit { export class StackSliderComponent implements AfterViewInit {
@ViewChild('ngxGlide') @ViewChild('ngxGlide')

View File

@@ -1,4 +1,4 @@
<div class="card container"> <div class="card container">
<div class="card-content"> <div class="card-content">
<div class="card-content-h"> <div class="card-content-h">
<h2 class="card-title"> <h2 class="card-title">
@@ -7,8 +7,10 @@
<p class="card-text">{{project.description}}</p> <p class="card-text">{{project.description}}</p>
</div> </div>
<div class="card-content-f row"> <div class="card-content-f row">
<div class="card-languages col-md-9" *ngIf="hasLanguage" id="language-chart"> @if (hasLanguage) {
<apx-chart *ngIf="chartOptions !== undefined" <div class="card-languages col-md-9" id="language-chart">
@if (chartOptions !== undefined) {
<apx-chart
[series]="chartOptions.series" [series]="chartOptions.series"
[colors]="chartOptions.colors" [colors]="chartOptions.colors"
[chart]="chartOptions.chart" [chart]="chartOptions.chart"
@@ -17,14 +19,18 @@
[plotOptions]="chartOptions.plotOptions" [plotOptions]="chartOptions.plotOptions"
[dataLabels]="chartOptions.dataLabels"> [dataLabels]="chartOptions.dataLabels">
</apx-chart> </apx-chart>
}
</div> </div>
}
<div class="card-stats" [ngClass]="hasLanguage ? 'col-md-3' : 'stats-inline'"> <div class="card-stats" [ngClass]="hasLanguage ? 'col-md-3' : 'stats-inline'">
<div class="stat-item" *ngIf="hasLicense"> @if (hasLicense) {
<div class="stat-item">
<div class="stat-icon"> <div class="stat-icon">
<fa-icon [icon]="faLicense"></fa-icon> <fa-icon [icon]="faLicense"></fa-icon>
</div> </div>
<span>{{project.license}}</span> <span>{{project.license}}</span>
</div> </div>
}
<div class="stat-item"> <div class="stat-item">
<div class="stat-icon"> <div class="stat-icon">
<fa-icon [icon]="faStars"></fa-icon> <fa-icon [icon]="faStars"></fa-icon>
@@ -46,4 +52,4 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -24,7 +24,8 @@ export type ChartOptions = {
@Component({ @Component({
selector: 'app-project-card', selector: 'app-project-card',
templateUrl: './project-card.component.html', templateUrl: './project-card.component.html',
styleUrls: ['./project-card.component.css'] styleUrls: ['./project-card.component.css'],
standalone: false
}) })
export class ProjectCardComponent implements OnInit { export class ProjectCardComponent implements OnInit {
@Input() inverted: boolean = false; @Input() inverted: boolean = false;

View File

@@ -1,6 +1,8 @@
<div class="container"> <div class="container">
<div *ngFor="let p of projects; index as i;trackBy: identifyProject"> @for (p of projects; track identifyProject(i, p); let i = $index) {
<div>
<app-project-card [project]="p" [inverted]="i % 2 !== 0"> <app-project-card [project]="p" [inverted]="i % 2 !== 0">
</app-project-card> </app-project-card>
</div> </div>
}
</div> </div>

View File

@@ -5,7 +5,8 @@ import {Project} from "../shared/model/project/project.model";
@Component({ @Component({
selector: 'app-projects', selector: 'app-projects',
templateUrl: './projects.component.html', templateUrl: './projects.component.html',
styleUrls: ['./projects.component.css'] styleUrls: ['./projects.component.css'],
standalone: false
}) })
export class ProjectsComponent implements OnInit { export class ProjectsComponent implements OnInit {
projects!: Project[]; projects!: Project[];

View File

@@ -7,36 +7,25 @@ import {Component, EventEmitter, Input, Output,} from '@angular/core';
styleUrls: ['./popup.component.css'], styleUrls: ['./popup.component.css'],
animations: [ animations: [
trigger('popupState', [ trigger('popupState', [
state( state('hide', style({
'hide',
style({
opacity: '0', opacity: '0',
zIndex: 2 zIndex: 2
}), })),
), state('show', style({
state(
'show',
style({
opacity: '1', opacity: '1',
zIndex: 2 zIndex: 2
}), })),
), transition('* => show', group([
transition(
'* => show',
group([
query('@*', animateChild(), { optional: true }), query('@*', animateChild(), { optional: true }),
animate('250ms ease-in'), animate('250ms ease-in'),
]), ])),
), transition('show => hide', group([
transition(
'show => hide',
group([
query('@*', animateChild(), { optional: true }), query('@*', animateChild(), { optional: true }),
animate('250ms ease-out'), animate('250ms ease-out'),
]), ])),
),
]), ]),
], ],
standalone: false
}) })
export class PopupComponent { export class PopupComponent {
@Input() @Input()

View File

@@ -7,36 +7,29 @@ import {Component, Input} from '@angular/core';
styleUrls: ['./slider-item.component.css'], styleUrls: ['./slider-item.component.css'],
animations: [ animations: [
trigger('animateSliderItem', [ trigger('animateSliderItem', [
state( state('hide', style({
'hide',
style({
opacity: '0', opacity: '0',
transform: 'translateX(150px)', transform: 'translateX(150px)',
}), }), {
{
params: { params: {
fadeInTime: 600, fadeInTime: 600,
fadeOutTime: 600, fadeOutTime: 600,
}, },
}, }),
), state('show', style({
state(
'show',
style({
opacity: '1', opacity: '1',
transform: 'translateX(0px)', transform: 'translateX(0px)',
}), }), {
{
params: { params: {
fadeOutTime: 600, fadeOutTime: 600,
fadeInTime: 600, fadeInTime: 600,
}, },
}, }),
),
transition('hide => show', animate(`{{ fadeInTime }}s ease-in`)), transition('hide => show', animate(`{{ fadeInTime }}s ease-in`)),
transition('show => hide', animate(`{{ fadeOutTime }}s ease-out`)), transition('show => hide', animate(`{{ fadeOutTime }}s ease-out`)),
]), ]),
], ],
standalone: false
}) })
export class SliderItemComponent { export class SliderItemComponent {
@Input() @Input()

View File

@@ -1,9 +1,10 @@
import {DOCUMENT} from '@angular/common';
import {AfterViewInit, Directive, ElementRef, EventEmitter, Inject, Input, OnDestroy, Output,} from '@angular/core'; import {AfterViewInit, Directive, ElementRef, EventEmitter, Inject, Input, OnDestroy, Output, DOCUMENT} from '@angular/core';
import {filter, fromEvent, Subscription,} from 'rxjs'; import {filter, fromEvent, Subscription,} from 'rxjs';
@Directive({ @Directive({
selector: '[appClickedOutside]', selector: '[appClickedOutside]',
standalone: false
}) })
export class ClickedOutsideDirective implements AfterViewInit, OnDestroy { export class ClickedOutsideDirective implements AfterViewInit, OnDestroy {
@Input() @Input()

View File

@@ -17,7 +17,11 @@ export class UpdateService {
} }
public checkForUpdates(): void { public checkForUpdates(): void {
this.swUpdate.available.subscribe((event) => this.promptUser()); this.swUpdate.versionUpdates.subscribe(versionUpdate => {
if (versionUpdate.type === 'VERSION_READY') {
this.promptUser();
}
})
} }
private promptUser(): void { private promptUser(): void {

View File

@@ -1,4 +1,4 @@
import {HttpClient, HttpHeaders, HttpParams} from '@angular/common/http'; import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {first, map, Observable, of, Subject,} from 'rxjs'; import {first, map, Observable, of, Subject,} from 'rxjs';
import {catchError} from 'rxjs/operators'; import {catchError} from 'rxjs/operators';
@@ -16,8 +16,6 @@ export class AuthService {
readonly BACKEND_PATH = environment.backendPath; readonly BACKEND_PATH = environment.backendPath;
readonly BACKEND_OAUTH_PATH = environment.backendOAuthPath;
constructor(private http: HttpClient) {} constructor(private http: HttpClient) {}
login(userAuthAtempt: User): void { login(userAuthAtempt: User): void {
this.validateUser(this.loginUser(userAuthAtempt)); this.validateUser(this.loginUser(userAuthAtempt));
@@ -25,14 +23,14 @@ export class AuthService {
googleLogin() { googleLogin() {
window.open( window.open(
this.BACKEND_OAUTH_PATH + '/oauth2/authorization/google', this.BACKEND_PATH + '/oauth2/authorization/google',
'_self', '_self',
); );
} }
githubLogin() { githubLogin() {
window.open( window.open(
this.BACKEND_OAUTH_PATH + '/oauth2/authorization/github', this.BACKEND_PATH + '/oauth2/authorization/github',
'_self', '_self',
); );
} }
@@ -112,7 +110,7 @@ export class AuthService {
}); });
return this.http return this.http
.get<User>(this.BACKEND_OAUTH_PATH + '/login/oauth2/code/google', { .get<User>(this.BACKEND_PATH + '/login/oauth2/code/google', {
withCredentials: true, withCredentials: true,
params: params, params: params,
}) })
@@ -125,7 +123,7 @@ export class AuthService {
}); });
return this.http return this.http
.get<User>(this.BACKEND_OAUTH_PATH + '/login/oauth2/code/github', { .get<User>(this.BACKEND_PATH + '/login/oauth2/code/github', {
withCredentials: true, withCredentials: true,
params: params, params: params,
}) })

View File

@@ -1,6 +1,6 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {Language, Project} from "../model/project/project.model"; import {Language, Project} from "../model/project/project.model";
import {HttpClient} from "@angular/common/http"; import { HttpClient } from "@angular/common/http";
import {map, Observable, switchMap, tap} from 'rxjs'; import {map, Observable, switchMap, tap} from 'rxjs';
import {environment} from 'src/environments/environment'; import {environment} from 'src/environments/environment';

View File

@@ -3,24 +3,18 @@ import {CommonModule} from '@angular/common';
import {ClickedOutsideDirective} from './directive/clicked-outside/clicked-outside.directive'; import {ClickedOutsideDirective} from './directive/clicked-outside/clicked-outside.directive';
import {SliderItemComponent} from './components/slider-item/slider-item.component'; import {SliderItemComponent} from './components/slider-item/slider-item.component';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {HttpClientModule} from '@angular/common/http'; import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import {PopupComponent} from './components/popup/popup.component'; import {PopupComponent} from './components/popup/popup.component';
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome'; import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
import {CookieConsentModule} from './cookie-consent/cookie-consent.module'; import {CookieConsentModule} from './cookie-consent/cookie-consent.module';
@NgModule({ @NgModule({ declarations: [
declarations: [
ClickedOutsideDirective, ClickedOutsideDirective,
SliderItemComponent, SliderItemComponent,
PopupComponent, PopupComponent,
], ],
imports: [ exports: [ClickedOutsideDirective, SliderItemComponent, PopupComponent], imports: [CommonModule,
CommonModule,
HttpClientModule,
BrowserAnimationsModule, BrowserAnimationsModule,
FontAwesomeModule, FontAwesomeModule,
CookieConsentModule, CookieConsentModule], providers: [provideHttpClient(withInterceptorsFromDi())] })
],
exports: [ClickedOutsideDirective, SliderItemComponent, PopupComponent],
})
export class SharedModule {} export class SharedModule {}

View File

@@ -1,6 +1,5 @@
export const environment = { export const environment = {
production: true, production: true,
backendPath: (<any>window)['env']['BACKEND_URL'], backendPath: (<any>window)['env']['BACKEND_URL'],
backendOAuthPath: (<any>window)['env']['BACKEND_OAUTH_URL'],
githubUser: (<any>window)['env']['GITHUB_USER'], githubUser: (<any>window)['env']['GITHUB_USER'],
}; };

View File

@@ -5,7 +5,6 @@
export const environment = { export const environment = {
production: false, production: false,
backendPath: 'http://localhost:8070', backendPath: 'http://localhost:8070',
backendOAuthPath: 'http://localhost:8070',
githubUser: 'HideyoshiNakazone', githubUser: 'HideyoshiNakazone',
}; };

View File

@@ -5,6 +5,7 @@
"baseUrl": "./", "baseUrl": "./",
"outDir": "./dist/out-tsc", "outDir": "./dist/out-tsc",
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"esModuleInterop": true,
"strict": true, "strict": true,
"noImplicitOverride": true, "noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true, "noPropertyAccessFromIndexSignature": true,
@@ -12,13 +13,15 @@
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"sourceMap": true, "sourceMap": true,
"declaration": false, "declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"moduleResolution": "node", "moduleResolution": "bundler",
"importHelpers": true, "importHelpers": true,
"target": "ES2022", "target": "ES2022",
"module": "es2020", "module": "es2020",
"lib": ["es2020", "dom"], "lib": [
"es2020",
"dom"
],
"useDefineForClassFields": false "useDefineForClassFields": false
}, },
"angularCompilerOptions": { "angularCompilerOptions": {