Merge pull request #91 from HideyoshiSolutions/develop
develop - feat: implements a better kubernetes deployment setup
This commit is contained in:
5
.dockerignore
Normal file
5
.dockerignore
Normal file
@@ -0,0 +1,5 @@
|
||||
.github
|
||||
.gitignore
|
||||
Dockerfile
|
||||
README.md
|
||||
.k8s
|
||||
108
.github/workflows/deploy.yml
vendored
Normal file
108
.github/workflows/deploy.yml
vendored
Normal 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 -
|
||||
61
.github/workflows/docker-publish.yml
vendored
61
.github/workflows/docker-publish.yml
vendored
@@ -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" }}'
|
||||
27
.github/workflows/vercel-cleanup-pr.yml
vendored
27
.github/workflows/vercel-cleanup-pr.yml
vendored
@@ -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
|
||||
|
||||
@@ -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
8
.k8s/config.yml
Normal 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
59
.k8s/deployment.yaml
Normal 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
25
.k8s/ingress.yaml
Normal 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
13
.k8s/service.yaml
Normal 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
|
||||
22
Dockerfile
22
Dockerfile
@@ -1,16 +1,30 @@
|
||||
FROM node:18-alpine
|
||||
FROM node:22.12-alpine AS base
|
||||
|
||||
FROM base AS build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
|
||||
RUN npm install
|
||||
|
||||
|
||||
FROM base AS prod
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=build /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
|
||||
RUN npm install -g @angular/cli@20.3.8
|
||||
|
||||
|
||||
RUN apk add --update gettext python3 py3-pip py3-setuptools make g++ && \
|
||||
rm -rf /var/cache/apk/*
|
||||
|
||||
RUN npm install
|
||||
RUN npm install -g @angular/cli@16
|
||||
|
||||
RUN npm run build:prod
|
||||
|
||||
EXPOSE 5000-7000
|
||||
|
||||
CMD ["npm", "run", "start:prod"]
|
||||
|
||||
65
angular.json
65
angular.json
@@ -11,12 +11,15 @@
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"builder": "@angular/build:application",
|
||||
"options": {
|
||||
"outputPath": "dist/frontend-hideyoshi.com",
|
||||
"outputPath": {
|
||||
"base": "dist/frontend-hideyoshi.com"
|
||||
},
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"polyfills": [
|
||||
"src/polyfills.ts"
|
||||
],
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"allowedCommonJsDependencies": [
|
||||
"ts-interface-checker",
|
||||
@@ -39,21 +42,21 @@
|
||||
"node_modules/bootstrap/dist/js/bootstrap.bundle.js",
|
||||
"node_modules/cookieconsent/build/cookieconsent.min.js"
|
||||
],
|
||||
"serviceWorker": true,
|
||||
"ngswConfigPath": "ngsw-config.json"
|
||||
"serviceWorker": "ngsw-config.json",
|
||||
"browser": "src/main.ts"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "1mb",
|
||||
"maximumError": "1.5mb"
|
||||
"maximumWarning": "2.5mb",
|
||||
"maximumError": "5mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "2.5kb",
|
||||
"maximumError": "5kb"
|
||||
"maximumWarning": "5kb",
|
||||
"maximumError": "10kb"
|
||||
}
|
||||
],
|
||||
"fileReplacements": [
|
||||
@@ -63,12 +66,10 @@
|
||||
}
|
||||
],
|
||||
"outputHashing": "all",
|
||||
"serviceWorker": true
|
||||
"serviceWorker": "ngsw-config.json"
|
||||
},
|
||||
"development": {
|
||||
"buildOptimizer": false,
|
||||
"optimization": false,
|
||||
"vendorChunk": true,
|
||||
"optimization": false,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true,
|
||||
"namedChunks": true
|
||||
@@ -77,25 +78,25 @@
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"builder": "@angular/build:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "frontend-hideyoshi.com:build:production"
|
||||
"buildTarget": "frontend-hideyoshi.com:build:production"
|
||||
},
|
||||
"development": {
|
||||
"browserTarget": "frontend-hideyoshi.com:build:development"
|
||||
"buildTarget": "frontend-hideyoshi.com:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"builder": "@angular/build:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "frontend-hideyoshi.com:build"
|
||||
"buildTarget": "frontend-hideyoshi.com:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"builder": "@angular/build:application",
|
||||
"options": {
|
||||
"main": "src/test.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
@@ -129,6 +130,30 @@
|
||||
},
|
||||
"@angular-eslint/schematics:library": {
|
||||
"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": "."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
31535
package-lock.json
generated
31535
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
158
package.json
158
package.json
@@ -1,81 +1,81 @@
|
||||
{
|
||||
"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,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^16.2.2",
|
||||
"@angular/cdk": "^16.2.1",
|
||||
"@angular/common": "^16.2.2",
|
||||
"@angular/compiler": "^16.2.2",
|
||||
"@angular/core": "^16.2.2",
|
||||
"@angular/forms": "^16.2.2",
|
||||
"@angular/material": "^16.2.1",
|
||||
"@angular/platform-browser": "^16.2.2",
|
||||
"@angular/platform-browser-dynamic": "^16.2.2",
|
||||
"@angular/router": "^16.2.2",
|
||||
"@angular/service-worker": "^16.2.2",
|
||||
"@fortawesome/angular-fontawesome": "^0.13.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.1.1",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.1.1",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.1.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.1.1",
|
||||
"@glidejs/glide": "^3.6.0",
|
||||
"@sinclair/typebox": "^0.32.4",
|
||||
"apexcharts": "^3.45.1",
|
||||
"bootstrap": "^4.6.2",
|
||||
"cookieconsent": "^3.1.1",
|
||||
"cors": "^2.8.5",
|
||||
"envsub": "^4.1.0",
|
||||
"express": "^4.18.1",
|
||||
"jquery": "^3.6.0",
|
||||
"ng-apexcharts": "^1.8.0",
|
||||
"ngx-cookie-service": "^16.0.1",
|
||||
"ngx-cookieconsent": "^4.0.2",
|
||||
"ngx-glide": "^16.0.0",
|
||||
"normalize.css": "^8.0.1",
|
||||
"rxjs": "~7.5.0",
|
||||
"ts-interface-checker": "^1.0.2",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.13.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^16.2.0",
|
||||
"@angular-eslint/builder": "^16.1.1",
|
||||
"@angular-eslint/eslint-plugin": "16.1.1",
|
||||
"@angular-eslint/eslint-plugin-template": "16.1.1",
|
||||
"@angular-eslint/schematics": "16.1.1",
|
||||
"@angular-eslint/template-parser": "16.1.1",
|
||||
"@angular/cli": "^16.2.0",
|
||||
"@angular/compiler-cli": "^16.2.2",
|
||||
"@types/jasmine": "~4.0.0",
|
||||
"@types/node": "^18.11.19",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.2",
|
||||
"@typescript-eslint/parser": "^5.59.2",
|
||||
"eslint": "^8.39.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-prettier": "^5.0.1",
|
||||
"jasmine-core": "~4.1.0",
|
||||
"karma": "~6.3.0",
|
||||
"karma-chrome-launcher": "~3.1.0",
|
||||
"karma-coverage": "~2.2.0",
|
||||
"karma-jasmine": "~5.0.0",
|
||||
"karma-jasmine-html-reporter": "~1.7.0",
|
||||
"prettier": "^3.0.3",
|
||||
"prettier-eslint": "^16.1.1",
|
||||
"ts-interface-builder": "^0.3.3",
|
||||
"typescript": "~4.9.5"
|
||||
"name": "frontend-hideyoshi.com",
|
||||
"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": {
|
||||
"@angular/animations": "^20.3.9",
|
||||
"@angular/cdk": "^19.2.19",
|
||||
"@angular/common": "^20.3.9",
|
||||
"@angular/compiler": "^20.3.9",
|
||||
"@angular/core": "^20.3.9",
|
||||
"@angular/forms": "^20.3.9",
|
||||
"@angular/material": "^19.2.19",
|
||||
"@angular/platform-browser": "^20.3.9",
|
||||
"@angular/platform-browser-dynamic": "^20.3.9",
|
||||
"@angular/router": "^20.3.9",
|
||||
"@angular/service-worker": "^20.3.9",
|
||||
"@fortawesome/angular-fontawesome": "^3.0.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.1.1",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.1.1",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.1.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.1.1",
|
||||
"@glidejs/glide": "^3.6.0",
|
||||
"@sinclair/typebox": "^0.32.4",
|
||||
"apexcharts": "^3.45.1",
|
||||
"bootstrap": "^4.6.2",
|
||||
"cookieconsent": "^3.1.1",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^17.2.3",
|
||||
"envsub": "^4.1.0",
|
||||
"express": "^4.18.1",
|
||||
"jquery": "^3.6.0",
|
||||
"ng-apexcharts": "^1.8.0",
|
||||
"ngx-cookie-service": "^20.1.1",
|
||||
"ngx-cookieconsent": "^4.0.2",
|
||||
"ngx-glide": "^16.0.0",
|
||||
"normalize.css": "^8.0.1",
|
||||
"rxjs": "~7.5.0",
|
||||
"ts-interface-checker": "^1.0.2",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.15.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-eslint/builder": "^19.8.1",
|
||||
"@angular-eslint/eslint-plugin": "^20.5.0",
|
||||
"@angular-eslint/eslint-plugin-template": "^20.5.0",
|
||||
"@angular-eslint/schematics": "^20.5.0",
|
||||
"@angular-eslint/template-parser": "^20.5.0",
|
||||
"@angular/build": "^20.3.8",
|
||||
"@angular/cli": "^20.3.8",
|
||||
"@angular/compiler-cli": "^20.3.9",
|
||||
"@types/jasmine": "~4.0.0",
|
||||
"@types/node": "^22.18.13",
|
||||
"@typescript-eslint/eslint-plugin": "^8.46.2",
|
||||
"@typescript-eslint/parser": "^8.46.2",
|
||||
"eslint": "^8.39.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-prettier": "^5.0.1",
|
||||
"jasmine-core": "~4.1.0",
|
||||
"karma": "~6.4.4",
|
||||
"karma-chrome-launcher": "~3.1.0",
|
||||
"karma-coverage": "~2.2.0",
|
||||
"karma-jasmine": "~5.0.0",
|
||||
"karma-jasmine-html-reporter": "~1.7.0",
|
||||
"prettier": "^3.0.3",
|
||||
"prettier-eslint": "^16.1.1",
|
||||
"ts-interface-builder": "^0.3.3",
|
||||
"typescript": "~5.8.3"
|
||||
},
|
||||
"proxy": {
|
||||
"/callback": {
|
||||
"target": "http://localhost:8070"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import {CookieConsertService} from './shared/cookie-consent/cookie-consert.servi
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css'],
|
||||
standalone: false
|
||||
})
|
||||
export class AppComponent implements OnInit {
|
||||
title = 'frontend-hideyoshi.com';
|
||||
|
||||
@@ -5,6 +5,7 @@ import {faGithub, faLinkedinIn, faTwitter,} from '@fortawesome/free-brands-svg-i
|
||||
selector: 'app-footer',
|
||||
templateUrl: './footer.component.html',
|
||||
styleUrls: ['./footer.component.css'],
|
||||
standalone: false
|
||||
})
|
||||
export class FooterComponent {
|
||||
_githubIcon = faGithub;
|
||||
|
||||
@@ -1,36 +1,44 @@
|
||||
<div
|
||||
class="dropdown"
|
||||
appClickedOutside
|
||||
(clickOutside)="onClickedOutside()"
|
||||
[includeClickedOutside]="[management]"
|
||||
[ignoreElementList]="ignoreClickOutside"
|
||||
[@dropdownState]="dropDownState"
|
||||
(@dropdownState.start)="$event.element.style.display = 'block'"
|
||||
class="dropdown"
|
||||
appClickedOutside
|
||||
(clickOutside)="onClickedOutside()"
|
||||
[includeClickedOutside]="[management]"
|
||||
[ignoreElementList]="ignoreClickOutside"
|
||||
[@dropdownState]="dropDownState"
|
||||
(@dropdownState.start)="$event.element.style.display = 'block'"
|
||||
(@dropdownState.done)="
|
||||
$event.element.style.display = state ? 'block' : 'none'
|
||||
"
|
||||
>
|
||||
<div class="info">
|
||||
<h3>{{ this.user ? this.user.username : "User Account" }}</h3>
|
||||
</div>
|
||||
<div #management>
|
||||
<ul class="user-management" *ngIf="!this.user">
|
||||
<li *ngFor="let option of mainOptions"
|
||||
class="dropdown-item" (click)=option.callback()>
|
||||
<div class="icon-box">
|
||||
<fa-icon [icon]="option.icon"></fa-icon>
|
||||
</div>
|
||||
<p>{{option.text}}</p>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="user-management" *ngIf="this.user">
|
||||
<li *ngFor="let option of userOptions"
|
||||
class="dropdown-item" (click)=option.callback()>
|
||||
<div class="icon-box">
|
||||
<fa-icon [icon]="option.icon"></fa-icon>
|
||||
</div>
|
||||
<p>{{option.text}}</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
>
|
||||
<div class="info">
|
||||
<h3>{{ this.user ? this.user.username : "User Account" }}</h3>
|
||||
</div>
|
||||
<div #management>
|
||||
@if (!this.user) {
|
||||
<ul class="user-management">
|
||||
@for (option of mainOptions; track option) {
|
||||
<li
|
||||
class="dropdown-item" (click)=option.callback()>
|
||||
<div class="icon-box">
|
||||
<fa-icon [icon]="option.icon"></fa-icon>
|
||||
</div>
|
||||
<p>{{option.text}}</p>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
@if (this.user) {
|
||||
<ul class="user-management">
|
||||
@for (option of userOptions; track option) {
|
||||
<li
|
||||
class="dropdown-item" (click)=option.callback()>
|
||||
<div class="icon-box">
|
||||
<fa-icon [icon]="option.icon"></fa-icon>
|
||||
</div>
|
||||
<p>{{option.text}}</p>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,22 +13,17 @@ import {Value} from "@sinclair/typebox/value";
|
||||
styleUrls: ['./header-dropdown.component.css'],
|
||||
animations: [
|
||||
trigger('dropdownState', [
|
||||
state(
|
||||
'hide',
|
||||
style({
|
||||
opacity: '0',
|
||||
}),
|
||||
),
|
||||
state(
|
||||
'show',
|
||||
style({
|
||||
opacity: '1',
|
||||
}),
|
||||
),
|
||||
state('hide', style({
|
||||
opacity: '0',
|
||||
})),
|
||||
state('show', style({
|
||||
opacity: '1',
|
||||
})),
|
||||
transition('hide => show', animate('20ms ease-in')),
|
||||
transition('show => hide', animate('5ms ease-out')),
|
||||
]),
|
||||
],
|
||||
standalone: false
|
||||
})
|
||||
export class HeaderDropdownComponent implements OnInit, OnDestroy {
|
||||
mainOptions: { text: string, icon: IconDefinition, callback: () => void }[] = [
|
||||
|
||||
@@ -6,6 +6,7 @@ import {AuthService} from 'src/app/shared/service/auth.service';
|
||||
selector: 'app-callback',
|
||||
templateUrl: './callback.component.html',
|
||||
styleUrls: ['./callback.component.css'],
|
||||
standalone: false
|
||||
})
|
||||
export class CallbackComponent implements OnInit {
|
||||
constructor(
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<div class="error-box" *ngIf="errorMessage">
|
||||
@if (errorMessage) {
|
||||
<div class="error-box">
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import {Component, Input} from '@angular/core';
|
||||
selector: 'app-error-box',
|
||||
templateUrl: './error-box.component.html',
|
||||
styleUrls: ['./error-box.component.css'],
|
||||
standalone: false
|
||||
})
|
||||
export class ErrorBoxComponent {
|
||||
@Input()
|
||||
|
||||
@@ -4,6 +4,7 @@ import {Component, EventEmitter, Input, Output} from '@angular/core';
|
||||
selector: 'app-help',
|
||||
templateUrl: './help.component.html',
|
||||
styleUrls: ['./help.component.css'],
|
||||
standalone: false
|
||||
})
|
||||
export class HelpComponent {
|
||||
@Input()
|
||||
|
||||
@@ -1,96 +1,98 @@
|
||||
<app-popup
|
||||
[state]="state"
|
||||
(stateChange)="onStateChange($event)"
|
||||
[ignoreClickOutside]="ignoreClickOutside"
|
||||
>
|
||||
<div
|
||||
class="container m-0 overflow-hidden"
|
||||
[@resizeContainerForErrorMessage]="hideErrorMessage()"
|
||||
[state]="state"
|
||||
(stateChange)="onStateChange($event)"
|
||||
[ignoreClickOutside]="ignoreClickOutside"
|
||||
>
|
||||
<div
|
||||
class="container m-0 overflow-hidden"
|
||||
[@resizeContainerForErrorMessage]="hideErrorMessage()"
|
||||
>
|
||||
<app-error-box
|
||||
[errorMessage]="errorMessage"
|
||||
[@showErrorMessage]="showErrorMessage()"
|
||||
>
|
||||
</app-error-box>
|
||||
<app-error-box
|
||||
[errorMessage]="errorMessage"
|
||||
[@showErrorMessage]="showErrorMessage()"
|
||||
>
|
||||
</app-error-box>
|
||||
|
||||
<div
|
||||
class="container authentication-container"
|
||||
[@hideAuthContainer]="hideErrorMessage()"
|
||||
(@hideAuthContainer.done)="hideAuthContainer($event)"
|
||||
>
|
||||
<div class="row">
|
||||
<div class="col-lg-6 authentication-body">
|
||||
<form [formGroup]="loginForm" (ngSubmit)="onLogin()">
|
||||
<div class="input-div">
|
||||
<fa-icon class="input-div-icon" [icon]="_userIcon">
|
||||
</fa-icon>
|
||||
<input
|
||||
type="text"
|
||||
id="username"
|
||||
formControlName="username"
|
||||
class="form-control"
|
||||
placeholder="Username"
|
||||
/>
|
||||
</div>
|
||||
<div class="input-div">
|
||||
<fa-icon
|
||||
class="input-div-icon"
|
||||
[icon]="_passwordIcon"
|
||||
>
|
||||
</fa-icon>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
formControlName="password"
|
||||
class="form-control"
|
||||
placeholder="Password"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
class="btn"
|
||||
[disabled]="loginForm.invalid"
|
||||
type="submit"
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="separator-line">
|
||||
<div class="line"></div>
|
||||
</div>
|
||||
<div class="col-lg-6 authentication-body">
|
||||
<button
|
||||
mat-button
|
||||
class="oauth-button d-flex justify-content-center align-items-center"
|
||||
[disabled]="isCookieBlocked"
|
||||
(click)="onGoogleLogin()"
|
||||
>
|
||||
<mat-icon
|
||||
*ngIf="!isCookieBlocked"
|
||||
style="width: 50px; height: 30px"
|
||||
svgIcon="google-logo"
|
||||
></mat-icon>
|
||||
<mat-icon
|
||||
*ngIf="isCookieBlocked"
|
||||
style="width: 50px; height: 30px"
|
||||
svgIcon="google-disabled-logo"
|
||||
></mat-icon>
|
||||
Login With Google
|
||||
</button>
|
||||
<button
|
||||
mat-button
|
||||
class="oauth-button d-flex justify-content-center align-items-center"
|
||||
[disabled]="isCookieBlocked"
|
||||
(click)="onGithubLogin()"
|
||||
>
|
||||
<mat-icon
|
||||
style="width: 50px; height: 30px"
|
||||
svgIcon="github-logo"
|
||||
></mat-icon>
|
||||
Login With Github
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="container authentication-container"
|
||||
[@hideAuthContainer]="hideErrorMessage()"
|
||||
(@hideAuthContainer.done)="hideAuthContainer($event)"
|
||||
>
|
||||
<div class="row">
|
||||
<div class="col-lg-6 authentication-body">
|
||||
<form [formGroup]="loginForm" (ngSubmit)="onLogin()">
|
||||
<div class="input-div">
|
||||
<fa-icon class="input-div-icon" [icon]="_userIcon">
|
||||
</fa-icon>
|
||||
<input
|
||||
type="text"
|
||||
id="username"
|
||||
formControlName="username"
|
||||
class="form-control"
|
||||
placeholder="Username"
|
||||
/>
|
||||
</div>
|
||||
<div class="input-div">
|
||||
<fa-icon
|
||||
class="input-div-icon"
|
||||
[icon]="_passwordIcon"
|
||||
>
|
||||
</fa-icon>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
formControlName="password"
|
||||
class="form-control"
|
||||
placeholder="Password"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
class="btn"
|
||||
[disabled]="loginForm.invalid"
|
||||
type="submit"
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="separator-line">
|
||||
<div class="line"></div>
|
||||
</div>
|
||||
<div class="col-lg-6 authentication-body">
|
||||
<button
|
||||
mat-button
|
||||
class="oauth-button d-flex justify-content-center align-items-center"
|
||||
[disabled]="isCookieBlocked"
|
||||
(click)="onGoogleLogin()"
|
||||
>
|
||||
@if (!isCookieBlocked) {
|
||||
<mat-icon
|
||||
style="width: 50px; height: 30px"
|
||||
svgIcon="google-logo"
|
||||
></mat-icon>
|
||||
}
|
||||
@if (isCookieBlocked) {
|
||||
<mat-icon
|
||||
style="width: 50px; height: 30px"
|
||||
svgIcon="google-disabled-logo"
|
||||
></mat-icon>
|
||||
}
|
||||
Login With Google
|
||||
</button>
|
||||
<button
|
||||
mat-button
|
||||
class="oauth-button d-flex justify-content-center align-items-center"
|
||||
[disabled]="isCookieBlocked"
|
||||
(click)="onGithubLogin()"
|
||||
>
|
||||
<mat-icon
|
||||
style="width: 50px; height: 30px"
|
||||
svgIcon="github-logo"
|
||||
></mat-icon>
|
||||
Login With Github
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</app-popup>
|
||||
|
||||
@@ -33,56 +33,39 @@ const GITHUB_LOGO_SVG = 'assets/img/providers/github.svg';
|
||||
styleUrls: ['./login.component.css'],
|
||||
animations: [
|
||||
trigger('resizeContainerForErrorMessage', [
|
||||
state(
|
||||
'hide',
|
||||
style({
|
||||
height: '100px',
|
||||
width: '320px',
|
||||
}),
|
||||
),
|
||||
transition(
|
||||
'show => hide',
|
||||
group([
|
||||
query('@*', animateChild(), { optional: true }),
|
||||
animate('1s ease'),
|
||||
]),
|
||||
),
|
||||
state('hide', style({
|
||||
height: '100px',
|
||||
width: '320px',
|
||||
})),
|
||||
transition('show => hide', group([
|
||||
query('@*', animateChild(), { optional: true }),
|
||||
animate('1s ease'),
|
||||
])),
|
||||
]),
|
||||
trigger('showErrorMessage', [
|
||||
state(
|
||||
'show',
|
||||
style({
|
||||
opacity: 1,
|
||||
height: '100px',
|
||||
width: '320px',
|
||||
}),
|
||||
),
|
||||
state(
|
||||
'hide',
|
||||
style({
|
||||
opacity: 0,
|
||||
height: '0px',
|
||||
width: '0px',
|
||||
}),
|
||||
),
|
||||
state('show', style({
|
||||
opacity: 1,
|
||||
height: '100px',
|
||||
width: '320px',
|
||||
})),
|
||||
state('hide', style({
|
||||
opacity: 0,
|
||||
height: '0px',
|
||||
width: '0px',
|
||||
})),
|
||||
transition('* => show', animate('500ms ease-in')),
|
||||
]),
|
||||
trigger('hideAuthContainer', [
|
||||
state(
|
||||
'hide',
|
||||
style({
|
||||
opacity: 0,
|
||||
}),
|
||||
),
|
||||
transition(
|
||||
'show => hide',
|
||||
group([
|
||||
query('@*', animateChild(), { optional: true }),
|
||||
animate('250ms ease-out'),
|
||||
]),
|
||||
),
|
||||
state('hide', style({
|
||||
opacity: 0,
|
||||
})),
|
||||
transition('show => hide', group([
|
||||
query('@*', animateChild(), { optional: true }),
|
||||
animate('250ms ease-out'),
|
||||
])),
|
||||
]),
|
||||
],
|
||||
standalone: false
|
||||
})
|
||||
export class LoginComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
@Input()
|
||||
|
||||
@@ -13,56 +13,39 @@ import {faFileUpload} from '@fortawesome/free-solid-svg-icons';
|
||||
styleUrls: ['./my-profile.component.css'],
|
||||
animations: [
|
||||
trigger('resizeContainerForErrorMessage', [
|
||||
state(
|
||||
'hide',
|
||||
style({
|
||||
height: '100px',
|
||||
width: '320px',
|
||||
}),
|
||||
),
|
||||
transition(
|
||||
'show => hide',
|
||||
group([
|
||||
query('@*', animateChild(), { optional: true }),
|
||||
animate('1s ease'),
|
||||
]),
|
||||
),
|
||||
state('hide', style({
|
||||
height: '100px',
|
||||
width: '320px',
|
||||
})),
|
||||
transition('show => hide', group([
|
||||
query('@*', animateChild(), { optional: true }),
|
||||
animate('1s ease'),
|
||||
])),
|
||||
]),
|
||||
trigger('showErrorMessage', [
|
||||
state(
|
||||
'show',
|
||||
style({
|
||||
opacity: 1,
|
||||
height: '100px',
|
||||
width: '320px',
|
||||
}),
|
||||
),
|
||||
state(
|
||||
'hide',
|
||||
style({
|
||||
opacity: 0,
|
||||
height: '0px',
|
||||
width: '0px',
|
||||
}),
|
||||
),
|
||||
state('show', style({
|
||||
opacity: 1,
|
||||
height: '100px',
|
||||
width: '320px',
|
||||
})),
|
||||
state('hide', style({
|
||||
opacity: 0,
|
||||
height: '0px',
|
||||
width: '0px',
|
||||
})),
|
||||
transition('* => show', animate('500ms ease-in')),
|
||||
]),
|
||||
trigger('hideAuthContainer', [
|
||||
state(
|
||||
'hide',
|
||||
style({
|
||||
opacity: 0,
|
||||
}),
|
||||
),
|
||||
transition(
|
||||
'show => hide',
|
||||
group([
|
||||
query('@*', animateChild(), { optional: true }),
|
||||
animate('250ms ease-out'),
|
||||
]),
|
||||
),
|
||||
state('hide', style({
|
||||
opacity: 0,
|
||||
})),
|
||||
transition('show => hide', group([
|
||||
query('@*', animateChild(), { optional: true }),
|
||||
animate('250ms ease-out'),
|
||||
])),
|
||||
]),
|
||||
],
|
||||
standalone: false
|
||||
})
|
||||
export class MyProfileComponent implements OnInit {
|
||||
@Input()
|
||||
|
||||
@@ -5,6 +5,7 @@ import {AuthService} from '../../../../shared/service/auth.service';
|
||||
selector: 'app-profile-picture-picker',
|
||||
templateUrl: './profile-picture-picker.component.html',
|
||||
styleUrls: ['./profile-picture-picker.component.css'],
|
||||
standalone: false
|
||||
})
|
||||
export class ProfilePicturePickerComponent {
|
||||
@Output()
|
||||
|
||||
@@ -22,56 +22,39 @@ const GITHUB_LOGO_SVG = 'assets/img/providers/github.svg';
|
||||
styleUrls: ['./signup.component.css'],
|
||||
animations: [
|
||||
trigger('resizeContainerForErrorMessage', [
|
||||
state(
|
||||
'hide',
|
||||
style({
|
||||
height: '100px',
|
||||
width: '320px',
|
||||
}),
|
||||
),
|
||||
transition(
|
||||
'show => hide',
|
||||
group([
|
||||
query('@*', animateChild(), { optional: true }),
|
||||
animate('1s ease'),
|
||||
]),
|
||||
),
|
||||
state('hide', style({
|
||||
height: '100px',
|
||||
width: '320px',
|
||||
})),
|
||||
transition('show => hide', group([
|
||||
query('@*', animateChild(), { optional: true }),
|
||||
animate('1s ease'),
|
||||
])),
|
||||
]),
|
||||
trigger('showErrorMessage', [
|
||||
state(
|
||||
'show',
|
||||
style({
|
||||
opacity: 1,
|
||||
height: '100px',
|
||||
width: '320px',
|
||||
}),
|
||||
),
|
||||
state(
|
||||
'hide',
|
||||
style({
|
||||
opacity: 0,
|
||||
height: '0px',
|
||||
width: '0px',
|
||||
}),
|
||||
),
|
||||
state('show', style({
|
||||
opacity: 1,
|
||||
height: '100px',
|
||||
width: '320px',
|
||||
})),
|
||||
state('hide', style({
|
||||
opacity: 0,
|
||||
height: '0px',
|
||||
width: '0px',
|
||||
})),
|
||||
transition('* => show', animate('500ms ease-in')),
|
||||
]),
|
||||
trigger('hideAuthContainer', [
|
||||
state(
|
||||
'hide',
|
||||
style({
|
||||
opacity: 0,
|
||||
}),
|
||||
),
|
||||
transition(
|
||||
'show => hide',
|
||||
group([
|
||||
query('@*', animateChild(), { optional: true }),
|
||||
animate('250ms ease-out'),
|
||||
]),
|
||||
),
|
||||
state('hide', style({
|
||||
opacity: 0,
|
||||
})),
|
||||
transition('show => hide', group([
|
||||
query('@*', animateChild(), { optional: true }),
|
||||
animate('250ms ease-out'),
|
||||
])),
|
||||
]),
|
||||
],
|
||||
standalone: false
|
||||
})
|
||||
export class SignupComponent implements OnInit {
|
||||
@Input()
|
||||
|
||||
@@ -7,18 +7,12 @@ import {Component, EventEmitter, Input, Output} from '@angular/core';
|
||||
styleUrls: ['./header-slider.component.css'],
|
||||
animations: [
|
||||
trigger('slideState', [
|
||||
state(
|
||||
'hide',
|
||||
style({
|
||||
transform: 'translateX(100%)',
|
||||
}),
|
||||
),
|
||||
state(
|
||||
'show',
|
||||
style({
|
||||
transform: 'translateX(0%)',
|
||||
}),
|
||||
),
|
||||
state('hide', style({
|
||||
transform: 'translateX(100%)',
|
||||
})),
|
||||
state('show', style({
|
||||
transform: 'translateX(0%)',
|
||||
})),
|
||||
transition('hide => show', [
|
||||
group([
|
||||
query('@*', animateChild(), { optional: true }),
|
||||
@@ -33,6 +27,7 @@ import {Component, EventEmitter, Input, Output} from '@angular/core';
|
||||
]),
|
||||
]),
|
||||
],
|
||||
standalone: false
|
||||
})
|
||||
export class HeaderSliderComponent {
|
||||
@Input()
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<div class="links-container">
|
||||
<div class="nav-links">
|
||||
<ul>
|
||||
<li
|
||||
*ngFor="let page of pages; let i = index"
|
||||
<div class="nav-links">
|
||||
<ul>
|
||||
@for (page of pages; track page; let i = $index) {
|
||||
<li
|
||||
[@animateSliderItem]="{
|
||||
value: itemStatus,
|
||||
params: {
|
||||
@@ -10,18 +10,19 @@
|
||||
fadeOutTime: 0.6 - i / 10
|
||||
}
|
||||
}"
|
||||
>
|
||||
<a [routerLink]="page.route">
|
||||
{{ page.name }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
>
|
||||
<a [routerLink]="page.route">
|
||||
{{ page.name }}
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="profile-container">
|
||||
<div
|
||||
class="profile"
|
||||
<div
|
||||
class="profile"
|
||||
[@animateSliderItem]="{
|
||||
value: itemStatus,
|
||||
params: {
|
||||
@@ -29,23 +30,25 @@
|
||||
fadeOutTime: 0.6 - (pages.length + 1) / 10
|
||||
}
|
||||
}"
|
||||
#profile
|
||||
>
|
||||
<div class="profile-btn" (click)="onProfileButtonClicked()">
|
||||
<fa-icon
|
||||
*ngIf="!loggedUser || !loggedUser.profilePictureUrl"
|
||||
class="fas fa-user"
|
||||
[icon]="userIcon"
|
||||
></fa-icon>
|
||||
<img
|
||||
*ngIf="!!loggedUser && !!loggedUser.profilePictureUrl"
|
||||
class="profile-picture"
|
||||
[ngSrc]="loggedUser.profilePictureUrl"
|
||||
width="50"
|
||||
height="50"
|
||||
alt="Profile Picture"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
#profile
|
||||
>
|
||||
<div class="profile-btn" (click)="onProfileButtonClicked()">
|
||||
@if (!loggedUser || !loggedUser.profilePictureUrl) {
|
||||
<fa-icon
|
||||
class="fas fa-user"
|
||||
[icon]="userIcon"
|
||||
></fa-icon>
|
||||
}
|
||||
@if (!!loggedUser && !!loggedUser.profilePictureUrl) {
|
||||
<img
|
||||
class="profile-picture"
|
||||
[ngSrc]="loggedUser.profilePictureUrl"
|
||||
width="50"
|
||||
height="50"
|
||||
alt="Profile Picture"
|
||||
priority
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,7 @@ import {Value} from "@sinclair/typebox/value";
|
||||
selector: 'app-nav-slider',
|
||||
templateUrl: './nav-slider.component.html',
|
||||
styleUrls: ['./nav-slider.component.css'],
|
||||
standalone: false
|
||||
})
|
||||
export class NavSliderComponent
|
||||
extends SliderItemComponent
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
<div class="user-container">
|
||||
<div class="user-options">
|
||||
<ul>
|
||||
<li
|
||||
*ngFor="
|
||||
let options of user ? userOptions : userlessOptions;
|
||||
let i = index
|
||||
"
|
||||
<div class="user-options">
|
||||
<ul>
|
||||
@for (
|
||||
options of user ? userOptions : userlessOptions; track
|
||||
options; let i = $index) {
|
||||
<li
|
||||
[@animateSliderItem]="{
|
||||
value: itemStatus,
|
||||
params: {
|
||||
@@ -13,11 +12,12 @@
|
||||
fadeOutTime: 0.6 - i / 10
|
||||
}
|
||||
}"
|
||||
>
|
||||
<a (click)="options.onClick()">
|
||||
{{ options.name }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
>
|
||||
<a (click)="options.onClick()">
|
||||
{{ options.name }}
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,6 +9,7 @@ import {Value} from "@sinclair/typebox/value";
|
||||
selector: 'app-user-slider',
|
||||
templateUrl: './user-slider.component.html',
|
||||
styleUrls: ['./user-slider.component.css'],
|
||||
standalone: false
|
||||
})
|
||||
export class UserSliderComponent extends SliderItemComponent implements OnInit {
|
||||
userlessOptions = [
|
||||
|
||||
@@ -1,90 +1,94 @@
|
||||
<div class="header">
|
||||
<div class="main" #header>
|
||||
<div class="logo">
|
||||
<a routerLink="">
|
||||
<img src="assets/img/logohideyoshi-white.png" alt="" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="nav-links">
|
||||
<ul class="link-container">
|
||||
<li *ngFor="let page of pages">
|
||||
<a [routerLink]="page.route">{{ page.name }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="profile" #profileDropdown>
|
||||
<div
|
||||
class="profile-btn"
|
||||
(click)="toogleProfileDropdown()"
|
||||
#profileBtn
|
||||
>
|
||||
<fa-icon
|
||||
*ngIf="!loggedUser || !loggedUser.profilePictureUrl"
|
||||
class="fas fa-user"
|
||||
[icon]="userIcon"
|
||||
></fa-icon>
|
||||
<img
|
||||
*ngIf="!!loggedUser && !!loggedUser.profilePictureUrl"
|
||||
class="profile-picture"
|
||||
[ngSrc]="loggedUser.profilePictureUrl"
|
||||
width="50"
|
||||
height="50"
|
||||
alt="Profile Picture"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
|
||||
<app-header-dropdown
|
||||
class="dropdown"
|
||||
(clickOutside)="closeDropdown()"
|
||||
[ignoreClickOutside]="[profileBtn]"
|
||||
[state]="profileDropdownState"
|
||||
(loginPopupState)="loginPopupStateChange($event)"
|
||||
(signupPopupState)="signupPopupStateChange($event)"
|
||||
(myProfilePopupState)="myProfilePopupStateChange($event)"
|
||||
(helpPopupState)="helpPopupStateChange($event)"
|
||||
>
|
||||
</app-header-dropdown>
|
||||
</div>
|
||||
<div class="burger-container" (click)="toogleNavSlider()">
|
||||
<div
|
||||
class="burger-menu"
|
||||
[ngClass]="{ open: navSliderStatus }"
|
||||
></div>
|
||||
</div>
|
||||
<div class="main" #header>
|
||||
<div class="logo">
|
||||
<a routerLink="">
|
||||
<img src="assets/img/logohideyoshi-white.png" alt="" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="nav-links">
|
||||
<ul class="link-container">
|
||||
@for (page of pages; track page) {
|
||||
<li>
|
||||
<a [routerLink]="page.route">{{ page.name }}</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="profile" #profileDropdown>
|
||||
<div
|
||||
class="profile-btn"
|
||||
(click)="toogleProfileDropdown()"
|
||||
#profileBtn
|
||||
>
|
||||
@if (!loggedUser || !loggedUser.profilePictureUrl) {
|
||||
<fa-icon
|
||||
class="fas fa-user"
|
||||
[icon]="userIcon"
|
||||
></fa-icon>
|
||||
}
|
||||
@if (!!loggedUser && !!loggedUser.profilePictureUrl) {
|
||||
<img
|
||||
class="profile-picture"
|
||||
[ngSrc]="loggedUser.profilePictureUrl"
|
||||
width="50"
|
||||
height="50"
|
||||
alt="Profile Picture"
|
||||
priority
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
||||
<app-header-dropdown
|
||||
class="dropdown"
|
||||
(clickOutside)="closeDropdown()"
|
||||
[ignoreClickOutside]="[profileBtn]"
|
||||
[state]="profileDropdownState"
|
||||
(loginPopupState)="loginPopupStateChange($event)"
|
||||
(signupPopupState)="signupPopupStateChange($event)"
|
||||
(myProfilePopupState)="myProfilePopupStateChange($event)"
|
||||
(helpPopupState)="helpPopupStateChange($event)"
|
||||
>
|
||||
</app-header-dropdown>
|
||||
</div>
|
||||
<div class="burger-container" (click)="toogleNavSlider()">
|
||||
<div
|
||||
class="burger-menu"
|
||||
[ngClass]="{ open: navSliderStatus }"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="slider-container" #nav>
|
||||
<app-header-slider
|
||||
[(state)]="navSliderStatus"
|
||||
[clickOutsideStopWatching]="userSliderStatus"
|
||||
[ignoreClickOutside]="[header, user]"
|
||||
<app-header-slider
|
||||
[(state)]="navSliderStatus"
|
||||
[clickOutsideStopWatching]="userSliderStatus"
|
||||
[ignoreClickOutside]="[header, user]"
|
||||
>
|
||||
<app-nav-slider
|
||||
[state]="navSliderStatus"
|
||||
(profileButtonClicked)="profileButtonClicked()"
|
||||
[pages]="pages"
|
||||
>
|
||||
</app-nav-slider>
|
||||
</app-header-slider>
|
||||
<app-nav-slider
|
||||
[state]="navSliderStatus"
|
||||
(profileButtonClicked)="profileButtonClicked()"
|
||||
[pages]="pages"
|
||||
>
|
||||
</app-nav-slider>
|
||||
</app-header-slider>
|
||||
</div>
|
||||
|
||||
<div class="slider-container" #user>
|
||||
<app-header-slider
|
||||
[(state)]="userSliderStatus"
|
||||
[ignoreClickOutside]="[header, nav]"
|
||||
<app-header-slider
|
||||
[(state)]="userSliderStatus"
|
||||
[ignoreClickOutside]="[header, nav]"
|
||||
>
|
||||
<app-user-slider
|
||||
[state]="userSliderStatus"
|
||||
(loginPopupState)="loginPopupStateChange($event)"
|
||||
(signupPopupState)="signupPopupStateChange($event)"
|
||||
(myProfilePopupState)="myProfilePopupStateChange($event)"
|
||||
(helpPopupState)="helpPopupStateChange($event)"
|
||||
>
|
||||
</app-user-slider>
|
||||
</app-header-slider>
|
||||
<app-user-slider
|
||||
[state]="userSliderStatus"
|
||||
(loginPopupState)="loginPopupStateChange($event)"
|
||||
(signupPopupState)="signupPopupStateChange($event)"
|
||||
(myProfilePopupState)="myProfilePopupStateChange($event)"
|
||||
(helpPopupState)="helpPopupStateChange($event)"
|
||||
>
|
||||
</app-user-slider>
|
||||
</app-header-slider>
|
||||
</div>
|
||||
|
||||
<div class="header-spacer"></div>
|
||||
|
||||
@@ -13,6 +13,7 @@ import {Value} from "@sinclair/typebox/value";
|
||||
selector: 'app-header',
|
||||
templateUrl: './header.component.html',
|
||||
styleUrls: ['./header.component.css'],
|
||||
standalone: false
|
||||
})
|
||||
export class HeaderComponent implements OnInit, OnDestroy {
|
||||
pages: { name: string; route: string }[] = [
|
||||
|
||||
@@ -4,6 +4,7 @@ import {Component} from '@angular/core';
|
||||
selector: 'app-home',
|
||||
templateUrl: './home.component.html',
|
||||
styleUrls: ['./home.component.css'],
|
||||
standalone: false
|
||||
})
|
||||
export class HomeComponent {
|
||||
constructor() {}
|
||||
|
||||
@@ -23,6 +23,7 @@ import {animate, state, style, transition, trigger} from "@angular/animations";
|
||||
]),
|
||||
])
|
||||
],
|
||||
standalone: false
|
||||
})
|
||||
export class StackCardComponent {
|
||||
@Input()
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<ngx-glide #ngxGlide class="container stack-slider" *ngIf="stacks && stacks.length > 0">
|
||||
<app-stack-card *ngFor="let stack of stacks" class="slider-card" [stack]="stack"
|
||||
@if (stacks && stacks.length > 0) {
|
||||
<ngx-glide #ngxGlide class="container stack-slider">
|
||||
@for (stack of stacks; track stack) {
|
||||
<app-stack-card class="slider-card" [stack]="stack"
|
||||
[inFocus]="isInFocus(stack)">
|
||||
</app-stack-card>
|
||||
</ngx-glide>
|
||||
</app-stack-card>
|
||||
}
|
||||
</ngx-glide>
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@ import {Stack} from "../../shared/model/stack/stack.model";
|
||||
@Component({
|
||||
selector: 'app-stack-slider',
|
||||
templateUrl: './stack-slider.component.html',
|
||||
styleUrls: ['./stack-slider.component.css']
|
||||
styleUrls: ['./stack-slider.component.css'],
|
||||
standalone: false
|
||||
})
|
||||
export class StackSliderComponent implements AfterViewInit {
|
||||
@ViewChild('ngxGlide')
|
||||
|
||||
@@ -1,49 +1,55 @@
|
||||
<div class="card container">
|
||||
<div class="card-content">
|
||||
<div class="card-content-h">
|
||||
<h2 class="card-title">
|
||||
<a [href]="project.link">{{project.name}}</a>
|
||||
</h2>
|
||||
<p class="card-text">{{project.description}}</p>
|
||||
</div>
|
||||
<div class="card-content-f row">
|
||||
<div class="card-languages col-md-9" *ngIf="hasLanguage" id="language-chart">
|
||||
<apx-chart *ngIf="chartOptions !== undefined"
|
||||
[series]="chartOptions.series"
|
||||
[colors]="chartOptions.colors"
|
||||
[chart]="chartOptions.chart"
|
||||
[labels]="chartOptions.labels"
|
||||
[responsive]="chartOptions.responsive"
|
||||
[plotOptions]="chartOptions.plotOptions"
|
||||
[dataLabels]="chartOptions.dataLabels">
|
||||
</apx-chart>
|
||||
</div>
|
||||
<div class="card-stats" [ngClass]="hasLanguage ? 'col-md-3' : 'stats-inline'">
|
||||
<div class="stat-item" *ngIf="hasLicense">
|
||||
<div class="stat-icon">
|
||||
<fa-icon [icon]="faLicense"></fa-icon>
|
||||
</div>
|
||||
<span>{{project.license}}</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-icon">
|
||||
<fa-icon [icon]="faStars"></fa-icon>
|
||||
</div>
|
||||
<span>{{project.stars}}</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-icon">
|
||||
<fa-icon [icon]="faCodeFork"></fa-icon>
|
||||
</div>
|
||||
<span>{{project.forks}}</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-icon">
|
||||
<fa-icon [icon]="faEye"></fa-icon>
|
||||
</div>
|
||||
<span>{{project.watchers}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card container">
|
||||
<div class="card-content">
|
||||
<div class="card-content-h">
|
||||
<h2 class="card-title">
|
||||
<a [href]="project.link">{{project.name}}</a>
|
||||
</h2>
|
||||
<p class="card-text">{{project.description}}</p>
|
||||
</div>
|
||||
<div class="card-content-f row">
|
||||
@if (hasLanguage) {
|
||||
<div class="card-languages col-md-9" id="language-chart">
|
||||
@if (chartOptions !== undefined) {
|
||||
<apx-chart
|
||||
[series]="chartOptions.series"
|
||||
[colors]="chartOptions.colors"
|
||||
[chart]="chartOptions.chart"
|
||||
[labels]="chartOptions.labels"
|
||||
[responsive]="chartOptions.responsive"
|
||||
[plotOptions]="chartOptions.plotOptions"
|
||||
[dataLabels]="chartOptions.dataLabels">
|
||||
</apx-chart>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div class="card-stats" [ngClass]="hasLanguage ? 'col-md-3' : 'stats-inline'">
|
||||
@if (hasLicense) {
|
||||
<div class="stat-item">
|
||||
<div class="stat-icon">
|
||||
<fa-icon [icon]="faLicense"></fa-icon>
|
||||
</div>
|
||||
<span>{{project.license}}</span>
|
||||
</div>
|
||||
}
|
||||
<div class="stat-item">
|
||||
<div class="stat-icon">
|
||||
<fa-icon [icon]="faStars"></fa-icon>
|
||||
</div>
|
||||
<span>{{project.stars}}</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-icon">
|
||||
<fa-icon [icon]="faCodeFork"></fa-icon>
|
||||
</div>
|
||||
<span>{{project.forks}}</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-icon">
|
||||
<fa-icon [icon]="faEye"></fa-icon>
|
||||
</div>
|
||||
<span>{{project.watchers}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -24,7 +24,8 @@ export type ChartOptions = {
|
||||
@Component({
|
||||
selector: 'app-project-card',
|
||||
templateUrl: './project-card.component.html',
|
||||
styleUrls: ['./project-card.component.css']
|
||||
styleUrls: ['./project-card.component.css'],
|
||||
standalone: false
|
||||
})
|
||||
export class ProjectCardComponent implements OnInit {
|
||||
@Input() inverted: boolean = false;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<div class="container">
|
||||
<div *ngFor="let p of projects; index as i;trackBy: identifyProject">
|
||||
<app-project-card [project]="p" [inverted]="i % 2 !== 0">
|
||||
</app-project-card>
|
||||
@for (p of projects; track identifyProject(i, p); let i = $index) {
|
||||
<div>
|
||||
<app-project-card [project]="p" [inverted]="i % 2 !== 0">
|
||||
</app-project-card>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,8 @@ import {Project} from "../shared/model/project/project.model";
|
||||
@Component({
|
||||
selector: 'app-projects',
|
||||
templateUrl: './projects.component.html',
|
||||
styleUrls: ['./projects.component.css']
|
||||
styleUrls: ['./projects.component.css'],
|
||||
standalone: false
|
||||
})
|
||||
export class ProjectsComponent implements OnInit {
|
||||
projects!: Project[];
|
||||
|
||||
@@ -7,36 +7,25 @@ import {Component, EventEmitter, Input, Output,} from '@angular/core';
|
||||
styleUrls: ['./popup.component.css'],
|
||||
animations: [
|
||||
trigger('popupState', [
|
||||
state(
|
||||
'hide',
|
||||
style({
|
||||
opacity: '0',
|
||||
zIndex: 2
|
||||
}),
|
||||
),
|
||||
state(
|
||||
'show',
|
||||
style({
|
||||
opacity: '1',
|
||||
zIndex: 2
|
||||
}),
|
||||
),
|
||||
transition(
|
||||
'* => show',
|
||||
group([
|
||||
query('@*', animateChild(), { optional: true }),
|
||||
animate('250ms ease-in'),
|
||||
]),
|
||||
),
|
||||
transition(
|
||||
'show => hide',
|
||||
group([
|
||||
query('@*', animateChild(), { optional: true }),
|
||||
animate('250ms ease-out'),
|
||||
]),
|
||||
),
|
||||
state('hide', style({
|
||||
opacity: '0',
|
||||
zIndex: 2
|
||||
})),
|
||||
state('show', style({
|
||||
opacity: '1',
|
||||
zIndex: 2
|
||||
})),
|
||||
transition('* => show', group([
|
||||
query('@*', animateChild(), { optional: true }),
|
||||
animate('250ms ease-in'),
|
||||
])),
|
||||
transition('show => hide', group([
|
||||
query('@*', animateChild(), { optional: true }),
|
||||
animate('250ms ease-out'),
|
||||
])),
|
||||
]),
|
||||
],
|
||||
standalone: false
|
||||
})
|
||||
export class PopupComponent {
|
||||
@Input()
|
||||
|
||||
@@ -7,36 +7,29 @@ import {Component, Input} from '@angular/core';
|
||||
styleUrls: ['./slider-item.component.css'],
|
||||
animations: [
|
||||
trigger('animateSliderItem', [
|
||||
state(
|
||||
'hide',
|
||||
style({
|
||||
opacity: '0',
|
||||
transform: 'translateX(150px)',
|
||||
}),
|
||||
{
|
||||
params: {
|
||||
fadeInTime: 600,
|
||||
fadeOutTime: 600,
|
||||
},
|
||||
state('hide', style({
|
||||
opacity: '0',
|
||||
transform: 'translateX(150px)',
|
||||
}), {
|
||||
params: {
|
||||
fadeInTime: 600,
|
||||
fadeOutTime: 600,
|
||||
},
|
||||
),
|
||||
state(
|
||||
'show',
|
||||
style({
|
||||
opacity: '1',
|
||||
transform: 'translateX(0px)',
|
||||
}),
|
||||
{
|
||||
params: {
|
||||
fadeOutTime: 600,
|
||||
fadeInTime: 600,
|
||||
},
|
||||
}),
|
||||
state('show', style({
|
||||
opacity: '1',
|
||||
transform: 'translateX(0px)',
|
||||
}), {
|
||||
params: {
|
||||
fadeOutTime: 600,
|
||||
fadeInTime: 600,
|
||||
},
|
||||
),
|
||||
}),
|
||||
transition('hide => show', animate(`{{ fadeInTime }}s ease-in`)),
|
||||
transition('show => hide', animate(`{{ fadeOutTime }}s ease-out`)),
|
||||
]),
|
||||
],
|
||||
standalone: false
|
||||
})
|
||||
export class SliderItemComponent {
|
||||
@Input()
|
||||
|
||||
@@ -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';
|
||||
|
||||
@Directive({
|
||||
selector: '[appClickedOutside]',
|
||||
standalone: false
|
||||
})
|
||||
export class ClickedOutsideDirective implements AfterViewInit, OnDestroy {
|
||||
@Input()
|
||||
|
||||
@@ -17,7 +17,11 @@ export class UpdateService {
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
@@ -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 {first, map, Observable, of, Subject,} from 'rxjs';
|
||||
import {catchError} from 'rxjs/operators';
|
||||
@@ -16,8 +16,6 @@ export class AuthService {
|
||||
|
||||
readonly BACKEND_PATH = environment.backendPath;
|
||||
|
||||
readonly BACKEND_OAUTH_PATH = environment.backendOAuthPath;
|
||||
|
||||
constructor(private http: HttpClient) {}
|
||||
login(userAuthAtempt: User): void {
|
||||
this.validateUser(this.loginUser(userAuthAtempt));
|
||||
@@ -25,14 +23,14 @@ export class AuthService {
|
||||
|
||||
googleLogin() {
|
||||
window.open(
|
||||
this.BACKEND_OAUTH_PATH + '/oauth2/authorization/google',
|
||||
this.BACKEND_PATH + '/oauth2/authorization/google',
|
||||
'_self',
|
||||
);
|
||||
}
|
||||
|
||||
githubLogin() {
|
||||
window.open(
|
||||
this.BACKEND_OAUTH_PATH + '/oauth2/authorization/github',
|
||||
this.BACKEND_PATH + '/oauth2/authorization/github',
|
||||
'_self',
|
||||
);
|
||||
}
|
||||
@@ -112,7 +110,7 @@ export class AuthService {
|
||||
});
|
||||
|
||||
return this.http
|
||||
.get<User>(this.BACKEND_OAUTH_PATH + '/login/oauth2/code/google', {
|
||||
.get<User>(this.BACKEND_PATH + '/login/oauth2/code/google', {
|
||||
withCredentials: true,
|
||||
params: params,
|
||||
})
|
||||
@@ -125,7 +123,7 @@ export class AuthService {
|
||||
});
|
||||
|
||||
return this.http
|
||||
.get<User>(this.BACKEND_OAUTH_PATH + '/login/oauth2/code/github', {
|
||||
.get<User>(this.BACKEND_PATH + '/login/oauth2/code/github', {
|
||||
withCredentials: true,
|
||||
params: params,
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
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 {environment} from 'src/environments/environment';
|
||||
|
||||
|
||||
@@ -3,24 +3,18 @@ import {CommonModule} from '@angular/common';
|
||||
import {ClickedOutsideDirective} from './directive/clicked-outside/clicked-outside.directive';
|
||||
import {SliderItemComponent} from './components/slider-item/slider-item.component';
|
||||
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 {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
|
||||
import {CookieConsentModule} from './cookie-consent/cookie-consent.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@NgModule({ declarations: [
|
||||
ClickedOutsideDirective,
|
||||
SliderItemComponent,
|
||||
PopupComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
HttpClientModule,
|
||||
exports: [ClickedOutsideDirective, SliderItemComponent, PopupComponent], imports: [CommonModule,
|
||||
BrowserAnimationsModule,
|
||||
FontAwesomeModule,
|
||||
CookieConsentModule,
|
||||
],
|
||||
exports: [ClickedOutsideDirective, SliderItemComponent, PopupComponent],
|
||||
})
|
||||
CookieConsentModule], providers: [provideHttpClient(withInterceptorsFromDi())] })
|
||||
export class SharedModule {}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
export const environment = {
|
||||
production: true,
|
||||
backendPath: (<any>window)['env']['BACKEND_URL'],
|
||||
backendOAuthPath: (<any>window)['env']['BACKEND_OAUTH_URL'],
|
||||
githubUser: (<any>window)['env']['GITHUB_USER'],
|
||||
};
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
export const environment = {
|
||||
production: false,
|
||||
backendPath: 'http://localhost:8070',
|
||||
backendOAuthPath: 'http://localhost:8070',
|
||||
githubUser: 'HideyoshiNakazone',
|
||||
};
|
||||
|
||||
|
||||
@@ -2,24 +2,27 @@
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"outDir": "./dist/out-tsc",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"downlevelIteration": true,
|
||||
"experimentalDecorators": true,
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"target": "ES2022",
|
||||
"module": "es2020",
|
||||
"lib": ["es2020", "dom"],
|
||||
"useDefineForClassFields": false
|
||||
"baseUrl": "./",
|
||||
"outDir": "./dist/out-tsc",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"experimentalDecorators": true,
|
||||
"moduleResolution": "bundler",
|
||||
"importHelpers": true,
|
||||
"target": "ES2022",
|
||||
"module": "es2020",
|
||||
"lib": [
|
||||
"es2020",
|
||||
"dom"
|
||||
],
|
||||
"useDefineForClassFields": false
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
|
||||
Reference in New Issue
Block a user