Refactor HTML and configuration files for improved structure and consistency

- Updated videos.html to enhance readability and maintainability.
- Refactored webpack.config.js for better organization and clarity.
- Cleaned up TypeScript configuration files (tsconfig.app.json, tsconfig.json, tsconfig.spec.json) for consistency in formatting.
- Adjusted docker-compose.yaml for improved formatting and readability.
This commit is contained in:
Marek Lesko
2025-08-05 06:35:56 +00:00
parent 83ca026a55
commit 02116aa3df
312 changed files with 45638 additions and 45628 deletions

View File

@@ -1,20 +1,19 @@
FROM mcr.microsoft.com/devcontainers/dotnet:1-8.0-bookworm
# Install SQL Tools: SQLPackage and sqlcmd
COPY mssql/installSQLtools.sh installSQLtools.sh
RUN bash ./installSQLtools.sh \
&& apt-get clean -y && rm -rf /var/lib/apt/lists/* /tmp/library-scripts
# RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \
# echo "deb http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google.list && \
# apt-get update && apt-get install -y google-chrome-stable xvfb
# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# && apt-get -y install --no-install-recommends <your-package-list-here>
ENV ASPNETCORE_HTTP_PORTS=5000
EXPOSE 4200
# [Optional] Uncomment this line to install global node packages.
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install --lts && nvm use --lts && npm install -g typescript" 2>&1
# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g <your-package-here>" 2>&1
FROM mcr.microsoft.com/devcontainers/dotnet:1-8.0-bookworm
# Install SQL Tools: SQLPackage and sqlcmd
COPY mssql/installSQLtools.sh installSQLtools.sh
RUN bash ./installSQLtools.sh && apt-get clean -y && rm -rf /var/lib/apt/lists/* /tmp/library-scripts
# RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \
# echo "deb http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google.list && \
# apt-get update && apt-get install -y google-chrome-stable xvfb
# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# && apt-get -y install --no-install-recommends <your-package-list-here>
ENV ASPNETCORE_HTTP_PORTS=5000
EXPOSE 4200
# [Optional] Uncomment this line to install global node packages.
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install --lts && nvm use --lts && npm install -g typescript" 2>&1
# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g <your-package-here>" 2>&1

View File

@@ -1,59 +1,59 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/dotnet-mssql
{
"name": ".NET (C#), Node.js (TypeScript) & MS SQL",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Configure tool-specific properties.
"customizations": {
// Configure properties specific to VS Code.
"vscode": {
// Set *default* container specific settings.json values on container create.
"settings": {
"mssql.connections": [
{
"server": "localhost,1433",
"database": "",
"authenticationType": "SqlLogin",
"user": "sa",
"password": "P@ssw0rd",
"emptyPasswordInput": false,
"savePassword": false,
"profileName": "mssql-container"
}
]
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"ms-dotnettools.csharp",
"ms-mssql.mssql"
]
}
},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [
4200,
5000,
5001,
1433
],
"portsAttributes": {
"5001": {
"protocol": "https"
},
"1433": {
"protocol": "tcp"
}
},
// postCreateCommand.sh parameters: $1=SA password, $2=dacpac path, $3=sql script(s) path
"containerEnv": {
"SA_PASSWORD": "P@ssw0rd",
"ACCEPT_EULA": "Y"
},
"postCreateCommand": "bash .devcontainer/mssql/postCreateCommand.sh 'P@ssw0rd' './bin/Debug/' './.devcontainer/mssql/'"
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/dotnet-mssql
{
"name": ".NET (C#), Node.js (TypeScript) & MS SQL",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Configure tool-specific properties.
"customizations": {
// Configure properties specific to VS Code.
"vscode": {
// Set *default* container specific settings.json values on container create.
"settings": {
"mssql.connections": [
{
"server": "localhost,1433",
"database": "",
"authenticationType": "SqlLogin",
"user": "sa",
"password": "P@ssw0rd",
"emptyPasswordInput": false,
"savePassword": false,
"profileName": "mssql-container"
}
]
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"ms-dotnettools.csharp",
"ms-mssql.mssql"
]
}
},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [
4200,
5000,
5001,
1433
],
"portsAttributes": {
"5001": {
"protocol": "https"
},
"1433": {
"protocol": "tcp"
}
},
// postCreateCommand.sh parameters: $1=SA password, $2=dacpac path, $3=sql script(s) path
"containerEnv": {
"SA_PASSWORD": "P@ssw0rd",
"ACCEPT_EULA": "Y"
},
"postCreateCommand": "bash .devcontainer/mssql/postCreateCommand.sh 'P@ssw0rd' './bin/Debug/' './.devcontainer/mssql/'"
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}

View File

@@ -1,34 +1,34 @@
version: '3'
services:
app:
build:
context: .
dockerfile: Dockerfile
volumes:
- ../..:/workspaces:cached
# Overrides default command so things don't shut down after the process ends.
command: sleep infinity
ports:
- "4200:4200"
# Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function.
network_mode: host
# Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
# user: root
# Use "forwardPorts" in **devcontainer.json** to forward an app port locally.
# (Adding the "ports" property to this file will not forward from a Codespace.)
db:
image: mcr.microsoft.com/mssql/server:2019-latest
restart: unless-stopped
network_mode: host
environment:
SA_PASSWORD: P@ssw0rd
ACCEPT_EULA: Y
# Add "forwardPorts": ["db:1433"] to **devcontainer.json** to forward MSSQL locally.
# (Adding the "ports" property to this file will not forward from a Codespace.)
version: '3'
services:
app:
build:
context: .
dockerfile: Dockerfile
volumes:
- ../..:/workspaces:cached
# Overrides default command so things don't shut down after the process ends.
command: sleep infinity
ports:
- "4200:4200"
# Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function.
network_mode: host
# Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
# user: root
# Use "forwardPorts" in **devcontainer.json** to forward an app port locally.
# (Adding the "ports" property to this file will not forward from a Codespace.)
db:
image: mcr.microsoft.com/mssql/server:2019-latest
restart: unless-stopped
network_mode: host
environment:
SA_PASSWORD: P@ssw0rd
ACCEPT_EULA: Y
# Add "forwardPorts": ["db:1433"] to **devcontainer.json** to forward MSSQL locally.
# (Adding the "ports" property to this file will not forward from a Codespace.)

View File

@@ -0,0 +1,64 @@
#!/bin/bash
dacpac="false"
sqlfiles="false"
SApassword=$1
dacpath=$2
sqlpath=$3
echo "SELECT * FROM SYS.DATABASES" | dd of=testsqlconnection.sql
for i in {1..60};
do
/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P $SApassword -d master -i testsqlconnection.sql > /dev/null
if [ $? -eq 0 ]
then
echo "SQL server ready"
break
else
echo "Not ready yet..."
sleep 1
fi
done
rm testsqlconnection.sql
for f in $dacpath/*
do
if [ $f == $dacpath/*".dacpac" ]
then
dacpac="true"
echo "Found dacpac $f"
fi
done
for f in $sqlpath/*
do
if [ $f == $sqlpath/*".sql" ]
then
sqlfiles="true"
echo "Found SQL file $f"
fi
done
if [ $sqlfiles == "true" ]
then
for f in $sqlpath/*
do
if [ $f == $sqlpath/*".sql" ]
then
echo "Executing $f"
/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P $SApassword -d master -i $f
fi
done
fi
if [ $dacpac == "true" ]
then
for f in $dacpath/*
do
if [ $f == $dacpath/*".dacpac" ]
then
dbname=$(basename $f ".dacpac")
echo "Deploying dacpac $f"
/opt/sqlpackage/sqlpackage /Action:Publish /SourceFile:$f /TargetServerName:localhost /TargetDatabaseName:$dbname /TargetUser:sa /TargetPassword:$SApassword
fi
done
fi

View File

@@ -5,60 +5,9 @@ SApassword=$1
dacpath=$2
sqlpath=$3
echo "SELECT * FROM SYS.DATABASES" | dd of=testsqlconnection.sql
for i in {1..60};
do
/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P $SApassword -d master -i testsqlconnection.sql > /dev/null
if [ $? -eq 0 ]
then
echo "SQL server ready"
break
else
echo "Not ready yet..."
sleep 1
fi
done
rm testsqlconnection.sql
for f in $dacpath/*
do
if [ $f == $dacpath/*".dacpac" ]
then
dacpac="true"
echo "Found dacpac $f"
fi
done
for f in $sqlpath/*
do
if [ $f == $sqlpath/*".sql" ]
then
sqlfiles="true"
echo "Found SQL file $f"
fi
done
if [ $sqlfiles == "true" ]
then
for f in $sqlpath/*
do
if [ $f == $sqlpath/*".sql" ]
then
echo "Executing $f"
/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P $SApassword -d master -i $f
fi
done
fi
if [ $dacpac == "true" ]
then
for f in $dacpath/*
do
if [ $f == $dacpath/*".dacpac" ]
then
dbname=$(basename $f ".dacpac")
echo "Deploying dacpac $f"
/opt/sqlpackage/sqlpackage /Action:Publish /SourceFile:$f /TargetServerName:localhost /TargetDatabaseName:$dbname /TargetUser:sa /TargetPassword:$SApassword
fi
done
fi
cd Api
dotnet restore ./Api.csproj
dotnet build ./Api.csproj
dotnet tool install --global dotnet-ef --version 8.*
export PATH="$PATH:/root/.dotnet/tools"
dotnet-ef database update --project ./Api.csproj --startup-project ./Api.csproj

View File

@@ -1,2 +0,0 @@
CREATE DATABASE ApplicationDB;
GO

View File

@@ -1,12 +1,12 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for more information:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
# https://containers.dev/guide/dependabot
version: 2
updates:
- package-ecosystem: "devcontainers"
directory: "/"
schedule:
interval: weekly
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for more information:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
# https://containers.dev/guide/dependabot
version: 2
updates:
- package-ecosystem: "devcontainers"
directory: "/"
schedule:
interval: weekly

View File

@@ -1,78 +1,78 @@
stages: # Define the stages of the pipeline.
- build
#- test
- release
semantic-release:
image: node:24
stage: release
script:
- apt update && apt install zip -y
- zip -r dist.zip Web/dist/Web/browser
- zip -r api.zip Api/build
- npm install --save-dev @semantic-release/gitlab
- npx semantic-release --debug
only:
- main
docker-build:
stage: build
image: docker:latest
tags:
- shared
services:
- name: docker:dind
alias: docker
variables:
DOCKER_DRIVER: overlay2
DOCKER_HOST: tcp://docker:2375
DOCKER_TLS_CERTDIR: ""
before_script:
- docker info
script:
- 'sed -i "s|\"apiEndpoint\": \"[^\"]*\"|\"apiEndpoint\": \"https\:\/\/""$PUBLIC_WEB_URL""\"|" Web/public/config.json'
- 'sed -i "s|\"AllowedHosts\": \"[^\"]*\"|\"AllowedHosts\": \"$PUBLIC_WEB_URL\"|" Api/appsettings.json'
- 'sed -i "s|\"CorsOrigins\": \"[^\"]*\"|\"CorsOrigins\": \"https\:\/\/""$PUBLIC_WEB_URL""\"|" Api/appsettings.json'
- docker build -t $CI_REGISTRY_IMAGE:latest -t $CI_REGISTRY_IMAGE:${CI_PIPELINE_IID} -f Dockerfile .
- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
- docker push $CI_REGISTRY_IMAGE:latest
- docker push $CI_REGISTRY_IMAGE:${CI_PIPELINE_IID}
only:
- dev
- main
docker-deploy:
stage: release
tags:
- production
- shell
script:
- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
- cd deployment
- docker compose pull
- docker compose -f docker-compose.yaml up -d
- docker image prune -f
only:
- dev
- main
update_db:
image: mcr.microsoft.com/dotnet/sdk:8.0
stage: release
needs: ["docker-deploy"]
script:
- cd Api
- dotnet restore ./Api.csproj
- dotnet build ./Api.csproj
- dotnet tool install --global dotnet-ef --version 8.*
- export PATH="$PATH:/root/.dotnet/tools"
- dotnet-ef database update --project ./Api.csproj --startup-project ./Api.csproj
only:
- dev
- main
tags:
- production
- docker
variables:
ConnectionStrings__DefaultConnection: "Server=localhost;Database=CentrumDb;User=sa;Password=$DB_PASSWORD;TrustServerCertificate=True;"
stages: # Define the stages of the pipeline.
- build
#- test
- release
semantic-release:
image: node:24
stage: release
script:
- apt update && apt install zip -y
- zip -r dist.zip Web/dist/Web/browser
- zip -r api.zip Api/build
- npm install --save-dev @semantic-release/gitlab
- npx semantic-release --debug
only:
- main
docker-build:
stage: build
image: docker:latest
tags:
- shared
services:
- name: docker:dind
alias: docker
variables:
DOCKER_DRIVER: overlay2
DOCKER_HOST: tcp://docker:2375
DOCKER_TLS_CERTDIR: ""
before_script:
- docker info
script:
- 'sed -i "s|\"apiEndpoint\": \"[^\"]*\"|\"apiEndpoint\": \"https\:\/\/""$PUBLIC_WEB_URL""\"|" Web/public/config.json'
- 'sed -i "s|\"AllowedHosts\": \"[^\"]*\"|\"AllowedHosts\": \"$PUBLIC_WEB_URL\"|" Api/appsettings.json'
- 'sed -i "s|\"CorsOrigins\": \"[^\"]*\"|\"CorsOrigins\": \"https\:\/\/""$PUBLIC_WEB_URL""\"|" Api/appsettings.json'
- docker build -t $CI_REGISTRY_IMAGE:latest -t $CI_REGISTRY_IMAGE:${CI_PIPELINE_IID} -f Dockerfile .
- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
- docker push $CI_REGISTRY_IMAGE:latest
- docker push $CI_REGISTRY_IMAGE:${CI_PIPELINE_IID}
only:
- dev
- main
docker-deploy:
stage: release
tags:
- production
- shell
script:
- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
- cd deployment
- docker compose pull
- docker compose -f docker-compose.yaml up -d
- docker image prune -f
only:
- dev
- main
update_db:
image: mcr.microsoft.com/dotnet/sdk:8.0
stage: release
needs: ["docker-deploy"]
script:
- cd Api
- dotnet restore ./Api.csproj
- dotnet build ./Api.csproj
- dotnet tool install --global dotnet-ef --version 8.*
- export PATH="$PATH:/root/.dotnet/tools"
- dotnet-ef database update --project ./Api.csproj --startup-project ./Api.csproj
only:
- dev
- main
tags:
- production
- docker
variables:
ConnectionStrings__DefaultConnection: "Server=localhost;Database=CentrumDb;User=sa;Password=$DB_PASSWORD;TrustServerCertificate=True;"

View File

@@ -1,2 +1,2 @@
coreDump:
enabled: false
coreDump:
enabled: false

View File

@@ -1,37 +1,37 @@
tasks:
hello:
name: Hello World
command: |
echo "Hello, World!"
triggeredBy:
- manual
# - postEnvironmentStart
# - postDevcontainerStart
services:
example-service:
name: Example Service
description: Example service simulating a backend
commands:
start: |
echo "Starting backend service..."
touch /tmp/backend.started
while true; do
sleep 1
date
done
ready: |
if [ -f /tmp/backend.started ]; then
echo "Backend service is ready"
exit 0
else
echo "Backend service is not ready"
exit 1
fi
# stop: |
# echo "Stopping backend service..."
# rm /tmp/backend.started
# pkill backend
triggeredBy:
- postEnvironmentStart
# - postDevcontainerStart
tasks:
hello:
name: Hello World
command: |
echo "Hello, World!"
triggeredBy:
- manual
# - postEnvironmentStart
# - postDevcontainerStart
services:
example-service:
name: Example Service
description: Example service simulating a backend
commands:
start: |
echo "Starting backend service..."
touch /tmp/backend.started
while true; do
sleep 1
date
done
ready: |
if [ -f /tmp/backend.started ]; then
echo "Backend service is ready"
exit 0
else
echo "Backend service is not ready"
exit 1
fi
# stop: |
# echo "Stopping backend service..."
# rm /tmp/backend.started
# pkill backend
triggeredBy:
- postEnvironmentStart
# - postDevcontainerStart

28
.vscode/launch.json vendored
View File

@@ -1,15 +1,15 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:4200",
"webRoot": "${workspaceFolder}/Web"
}
]
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:4200",
"webRoot": "${workspaceFolder}/Web"
}
]
}

80
.vscode/tasks.json vendored
View File

@@ -1,41 +1,41 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/Api/Api.sln",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/Api/Api.sln",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"--project",
"${workspaceFolder}/Api/Api.sln"
],
"problemMatcher": "$msCompile"
}
]
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/Api/Api.sln",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/Api/Api.sln",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"--project",
"${workspaceFolder}/Api/Api.sln"
],
"problemMatcher": "$msCompile"
}
]
}

8
Api/.gitignore vendored
View File

@@ -1,5 +1,5 @@
obj/
bin/
.vs/
build/
obj/
bin/
.vs/
build/
**/core

View File

@@ -1,70 +1,70 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Api.Models;
using Microsoft.AspNetCore.Authorization;
namespace Api.Controllers
{
[ApiController]
[Authorize]
[Route("api/product")]
public class ProductController : ControllerBase
{
private readonly AppDbContext _context;
public ProductController(AppDbContext context)
{
_context = context;
}
// GET: api/Product
[HttpGet]
public async Task<ActionResult<IEnumerable<Product>>> GetProducts([FromQuery] int? id = null)
{
if (id.HasValue)
{
return await _context.Products
.Where(p => p.Id == id.Value)
.ToListAsync();
}
else
return await _context.Products.ToListAsync();
}
// POST: api/Product
[HttpPost]
public async Task<ActionResult<Product>> PostProduct([FromBody] Product product)
{
_context.Products.Add(product);
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(GetProducts), new { id = product.Id }, product);
}
// PUT: api/Product/{id}
[HttpPut("{id}")]
public async Task<IActionResult> PutProduct(int id, [FromBody] Product product)
{
if (id != product.Id)
{
return BadRequest();
}
_context.Entry(product).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!await _context.Products.AnyAsync(e => e.Id == id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
}
}
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Api.Models;
using Microsoft.AspNetCore.Authorization;
namespace Api.Controllers
{
[ApiController]
[Authorize]
[Route("api/product")]
public class ProductController : ControllerBase
{
private readonly AppDbContext _context;
public ProductController(AppDbContext context)
{
_context = context;
}
// GET: api/Product
[HttpGet]
public async Task<ActionResult<IEnumerable<Product>>> GetProducts([FromQuery] int? id = null)
{
if (id.HasValue)
{
return await _context.Products
.Where(p => p.Id == id.Value)
.ToListAsync();
}
else
return await _context.Products.ToListAsync();
}
// POST: api/Product
[HttpPost]
public async Task<ActionResult<Product>> PostProduct([FromBody] Product product)
{
_context.Products.Add(product);
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(GetProducts), new { id = product.Id }, product);
}
// PUT: api/Product/{id}
[HttpPut("{id}")]
public async Task<IActionResult> PutProduct(int id, [FromBody] Product product)
{
if (id != product.Id)
{
return BadRequest();
}
_context.Entry(product).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!await _context.Products.AnyAsync(e => e.Id == id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
}
}

View File

@@ -1,56 +1,56 @@
// <auto-generated />
using Api.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Api.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20250727154636_InitialCreate")]
partial class InitialCreate
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.7")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Api.Models.Product", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Name")
.HasColumnType("nvarchar(max)");
b.Property<decimal>("Price")
.HasColumnType("decimal(18,2)");
b.HasKey("Id");
b.ToTable("Products");
b.HasData(
new
{
Id = 1,
Name = "Sample Product",
Price = 9.99m
});
});
#pragma warning restore 612, 618
}
}
}
// <auto-generated />
using Api.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Api.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20250727154636_InitialCreate")]
partial class InitialCreate
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.7")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Api.Models.Product", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Name")
.HasColumnType("nvarchar(max)");
b.Property<decimal>("Price")
.HasColumnType("decimal(18,2)");
b.HasKey("Id");
b.ToTable("Products");
b.HasData(
new
{
Id = 1,
Name = "Sample Product",
Price = 9.99m
});
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -1,40 +1,40 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Api.Migrations
{
/// <inheritdoc />
public partial class InitialCreate : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Products",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(max)", nullable: true),
Price = table.Column<decimal>(type: "decimal(18,2)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Products", x => x.Id);
});
migrationBuilder.InsertData(
table: "Products",
columns: new[] { "Id", "Name", "Price" },
values: new object[] { 1, "Sample Product", 9.99m });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Products");
}
}
}
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Api.Migrations
{
/// <inheritdoc />
public partial class InitialCreate : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Products",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(max)", nullable: true),
Price = table.Column<decimal>(type: "decimal(18,2)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Products", x => x.Id);
});
migrationBuilder.InsertData(
table: "Products",
columns: new[] { "Id", "Name", "Price" },
values: new object[] { 1, "Sample Product", 9.99m });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Products");
}
}
}

View File

@@ -1,53 +1,53 @@
// <auto-generated />
using Api.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Api.Migrations
{
[DbContext(typeof(AppDbContext))]
partial class AppDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.7")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Api.Models.Product", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Name")
.HasColumnType("nvarchar(max)");
b.Property<decimal>("Price")
.HasColumnType("decimal(18,2)");
b.HasKey("Id");
b.ToTable("Products");
b.HasData(
new
{
Id = 1,
Name = "Sample Product",
Price = 9.99m
});
});
#pragma warning restore 612, 618
}
}
}
// <auto-generated />
using Api.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Api.Migrations
{
[DbContext(typeof(AppDbContext))]
partial class AppDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.7")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Api.Models.Product", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Name")
.HasColumnType("nvarchar(max)");
b.Property<decimal>("Price")
.HasColumnType("decimal(18,2)");
b.HasKey("Id");
b.ToTable("Products");
b.HasData(
new
{
Id = 1,
Name = "Sample Product",
Price = 9.99m
});
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -1,27 +1,27 @@
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
namespace Api.Models
{
public class Product
{
public int Id { get; set; }
public string? Name { get; set; }
public decimal Price { get; set; }
}
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
public DbSet<Product> Products { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Seed data
modelBuilder.Entity<Product>().HasData(
new Product { Id = 1, Name = "Sample Product", Price = 9.99M }
);
}
}
}
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
namespace Api.Models
{
public class Product
{
public int Id { get; set; }
public string? Name { get; set; }
public decimal Price { get; set; }
}
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
public DbSet<Product> Products { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Seed data
modelBuilder.Entity<Product>().HasData(
new Product { Id = 1, Name = "Sample Product", Price = 9.99M }
);
}
}
}

View File

@@ -76,7 +76,7 @@ namespace Api
.AddRewrite(@$"^(?!.*?\b({rewriteString}))^(?!.*?\.\b(jpg|jpeg|png|svg|ttf|woff|woff2|html|js|json|css|ico))", "index.html", false);
app.UseRewriter(rewriteOptions);
// Serve static files from the Angular app
if (app.Environment.IsDevelopment())
{
var currentDirectory = Directory.GetCurrentDirectory();

View File

@@ -1,24 +1,24 @@
# Build Angular Web app
FROM node:24 AS web-build
WORKDIR /app/web
COPY Web/package*.json ./
RUN npm install
COPY Web/ ./
RUN npm run build -- --output-path=dist/Web/browser
# Build .NET Api app
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS api-build
WORKDIR /app/api
COPY Api/*.csproj ./
RUN dotnet restore
COPY Api/ ./
RUN dotnet publish Api.csproj -c Release -o /app/api/build
# Final runtime image
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
WORKDIR /app
COPY --from=api-build /app/api/build ./
COPY --from=web-build /app/web/dist/Web/browser/**/* ./wwwroot/
ENV ASPNETCORE_URLS=http://+:5000
EXPOSE 5000
# Build Angular Web app
FROM node:24 AS web-build
WORKDIR /app/web
COPY Web/package*.json ./
RUN npm install
COPY Web/ ./
RUN npm run build -- --output-path=dist/Web/browser
# Build .NET Api app
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS api-build
WORKDIR /app/api
COPY Api/*.csproj ./
RUN dotnet restore
COPY Api/ ./
RUN dotnet publish Api.csproj -c Release -o /app/api/build
# Final runtime image
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
WORKDIR /app
COPY --from=api-build /app/api/build ./
COPY --from=web-build /app/web/dist/Web/browser/**/* ./wwwroot/
ENV ASPNETCORE_URLS=http://+:5000
EXPOSE 5000
ENTRYPOINT ["dotnet", "Api.dll"]

186
README.md
View File

@@ -1,93 +1,93 @@
# Centrum
![release](https://gitlab.lesko.me/marek/centrum/-/badges/release.svg)
[![pipeline status](https://gitlab.lesko.me/marek/centrum/badges/main/pipeline.svg)](https://gitlab.lesko.me/marek/centrum/-/commits/main)
## Getting started
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
## Add your files
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
- [ ] [Add files using the command line](https://docs.gitlab.com/topics/git/add_files/#add-files-to-a-git-repository) or push an existing Git repository with the following command:
```
cd existing_repo
git remote add origin https://gitlab.lesko.me/marek/centrum.git
git branch -M main
git push -uf origin main
```
## Integrate with your tools
- [ ] [Set up project integrations](https://gitlab.lesko.me/marek/centrum/-/settings/integrations)
## Collaborate with your team
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
- [ ] [Set auto-merge](https://docs.gitlab.com/user/project/merge_requests/auto_merge/)
## Test and Deploy
Use the built-in continuous integration in GitLab.
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/)
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
***
# Editing this README
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template.
## Suggestions for a good README
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
## Name
Choose a self-explaining name for your project.
## Description
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
## Badges
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
## Visuals
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
## Installation
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
## Usage
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
## Support
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
## Roadmap
If you have ideas for releases in the future, it is a good idea to list them in the README.
## Contributing
State if you are open to contributions and what your requirements are for accepting them.
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
## Authors and acknowledgment
Show your appreciation to those who have contributed to the project.
## License
For open source projects, say how it is licensed.
## Project status
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
# Centrum
![release](https://gitlab.lesko.me/marek/centrum/-/badges/release.svg)
[![pipeline status](https://gitlab.lesko.me/marek/centrum/badges/main/pipeline.svg)](https://gitlab.lesko.me/marek/centrum/-/commits/main)
## Getting started
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
## Add your files
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
- [ ] [Add files using the command line](https://docs.gitlab.com/topics/git/add_files/#add-files-to-a-git-repository) or push an existing Git repository with the following command:
```
cd existing_repo
git remote add origin https://gitlab.lesko.me/marek/centrum.git
git branch -M main
git push -uf origin main
```
## Integrate with your tools
- [ ] [Set up project integrations](https://gitlab.lesko.me/marek/centrum/-/settings/integrations)
## Collaborate with your team
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
- [ ] [Set auto-merge](https://docs.gitlab.com/user/project/merge_requests/auto_merge/)
## Test and Deploy
Use the built-in continuous integration in GitLab.
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/)
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
***
# Editing this README
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template.
## Suggestions for a good README
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
## Name
Choose a self-explaining name for your project.
## Description
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
## Badges
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
## Visuals
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
## Installation
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
## Usage
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
## Support
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
## Roadmap
If you have ideas for releases in the future, it is a good idea to list them in the README.
## Contributing
State if you are open to contributions and what your requirements are for accepting them.
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
## Authors and acknowledgment
Show your appreciation to those who have contributed to the project.
## License
For open source projects, say how it is licensed.
## Project status
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.

2
Tests/.gitignore vendored
View File

@@ -1,2 +1,2 @@
obj/
obj/
bin/

View File

@@ -1,38 +1,38 @@
namespace Tests.Tests
{
public class IntegrationTest1
{
// Instructions:
// 1. Add a project reference to the target AppHost project, e.g.:
//
// <ItemGroup>
// <ProjectReference Include="../MyAspireApp.AppHost/MyAspireApp.AppHost.csproj" />
// </ItemGroup>
//
// 2. Uncomment the following example test and update 'Projects.MyAspireApp_AppHost' to match your AppHost project:
//
// [Fact]
// public async Task GetWebResourceRootReturnsOkStatusCode()
// {
// // Arrange
// var appHost = await DistributedApplicationTestingBuilder.CreateAsync<Projects.MyAspireApp_AppHost>();
// appHost.Services.ConfigureHttpClientDefaults(clientBuilder =>
// {
// clientBuilder.AddStandardResilienceHandler();
// });
// // To output logs to the xUnit.net ITestOutputHelper, consider adding a package from https://www.nuget.org/packages?q=xunit+logging
//
// await using var app = await appHost.BuildAsync();
// var resourceNotificationService = app.Services.GetRequiredService<ResourceNotificationService>();
// await app.StartAsync();
// // Act
// var httpClient = app.CreateHttpClient("webfrontend");
// await resourceNotificationService.WaitForResourceAsync("webfrontend", KnownResourceStates.Running).WaitAsync(TimeSpan.FromSeconds(30));
// var response = await httpClient.GetAsync("/");
// // Assert
// Assert.Equal(HttpStatusCode.OK, response.StatusCode);
// }
}
}
namespace Tests.Tests
{
public class IntegrationTest1
{
// Instructions:
// 1. Add a project reference to the target AppHost project, e.g.:
//
// <ItemGroup>
// <ProjectReference Include="../MyAspireApp.AppHost/MyAspireApp.AppHost.csproj" />
// </ItemGroup>
//
// 2. Uncomment the following example test and update 'Projects.MyAspireApp_AppHost' to match your AppHost project:
//
// [Fact]
// public async Task GetWebResourceRootReturnsOkStatusCode()
// {
// // Arrange
// var appHost = await DistributedApplicationTestingBuilder.CreateAsync<Projects.MyAspireApp_AppHost>();
// appHost.Services.ConfigureHttpClientDefaults(clientBuilder =>
// {
// clientBuilder.AddStandardResilienceHandler();
// });
// // To output logs to the xUnit.net ITestOutputHelper, consider adding a package from https://www.nuget.org/packages?q=xunit+logging
//
// await using var app = await appHost.BuildAsync();
// var resourceNotificationService = app.Services.GetRequiredService<ResourceNotificationService>();
// await app.StartAsync();
// // Act
// var httpClient = app.CreateHttpClient("webfrontend");
// await resourceNotificationService.WaitForResourceAsync("webfrontend", KnownResourceStates.Running).WaitAsync(TimeSpan.FromSeconds(30));
// var response = await httpClient.GetAsync("/");
// // Assert
// Assert.Equal(HttpStatusCode.OK, response.StatusCode);
// }
}
}

View File

@@ -1,27 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.Testing" Version="9.3.1" />
<PackageReference Include="coverlet.collector" Version="6.0.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
</ItemGroup>
<ItemGroup>
<Using Include="System.Net" />
<Using Include="Microsoft.Extensions.DependencyInjection" />
<Using Include="Aspire.Hosting.ApplicationModel" />
<Using Include="Aspire.Hosting.Testing" />
<Using Include="Xunit" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.Testing" Version="9.3.1" />
<PackageReference Include="coverlet.collector" Version="6.0.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
</ItemGroup>
<ItemGroup>
<Using Include="System.Net" />
<Using Include="Microsoft.Extensions.DependencyInjection" />
<Using Include="Aspire.Hosting.ApplicationModel" />
<Using Include="Aspire.Hosting.Testing" />
<Using Include="Xunit" />
</ItemGroup>
</Project>

View File

@@ -1,17 +1,17 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
ij_typescript_use_double_quotes = false
[*.md]
max_line_length = off
trim_trailing_whitespace = false
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
ij_typescript_use_double_quotes = false
[*.md]
max_line_length = off
trim_trailing_whitespace = false

84
Web/.gitignore vendored
View File

@@ -1,42 +1,42 @@
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
# Compiled output
/dist
/tmp
/out-tsc
/bazel-out
# Node
/node_modules
npm-debug.log
yarn-error.log
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
# System files
.DS_Store
Thumbs.db
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
# Compiled output
/dist
/tmp
/out-tsc
/bazel-out
# Node
/node_modules
npm-debug.log
yarn-error.log
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
# System files
.DS_Store
Thumbs.db

View File

@@ -1,4 +1,4 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
"recommendations": ["angular.ng-template"]
}
{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
"recommendations": ["angular.ng-template"]
}

View File

@@ -1,21 +1,21 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "ng serve",
"type": "chrome",
"request": "launch",
"preLaunchTask": "npm: start",
"url": "http://localhost:4200/",
"webRoot": "${workspaceFolder}/Web",
},
{
"name": "ng test",
"type": "chrome",
"request": "launch",
"preLaunchTask": "npm: test",
"url": "http://localhost:9876/debug.html"
}
]
}
{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "ng serve",
"type": "chrome",
"request": "launch",
"preLaunchTask": "npm: start",
"url": "http://localhost:4200/",
"webRoot": "${workspaceFolder}/Web",
},
{
"name": "ng test",
"type": "chrome",
"request": "launch",
"preLaunchTask": "npm: test",
"url": "http://localhost:9876/debug.html"
}
]
}

View File

@@ -1,42 +1,42 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "start",
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": {
"regexp": "(.*?)"
},
"endsPattern": {
"regexp": "bundle generation complete"
}
}
}
},
{
"type": "npm",
"script": "test",
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": {
"regexp": "(.*?)"
},
"endsPattern": {
"regexp": "bundle generation complete"
}
}
}
}
]
}
{
// For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "start",
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": {
"regexp": "(.*?)"
},
"endsPattern": {
"regexp": "bundle generation complete"
}
}
}
},
{
"type": "npm",
"script": "test",
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": {
"regexp": "(.*?)"
},
"endsPattern": {
"regexp": "bundle generation complete"
}
}
}
}
]
}

View File

@@ -1,59 +1,59 @@
# Web
This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 20.0.6.
## Development server
To start a local development server, run:
```bash
ng serve
```
Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files.
## Code scaffolding
Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
```bash
ng generate component component-name
```
For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
```bash
ng generate --help
```
## Building
To build the project run:
```bash
ng build
```
This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed.
## Running unit tests
To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
```bash
ng test
```
## Running end-to-end tests
For end-to-end (e2e) testing, run:
```bash
ng e2e
```
Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
## Additional Resources
For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
# Web
This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 20.0.6.
## Development server
To start a local development server, run:
```bash
ng serve
```
Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files.
## Code scaffolding
Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
```bash
ng generate component component-name
```
For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
```bash
ng generate --help
```
## Building
To build the project run:
```bash
ng build
```
This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed.
## Running unit tests
To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
```bash
ng test
```
## Running end-to-end tests
For end-to-end (e2e) testing, run:
```bash
ng e2e
```
Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
## Additional Resources
For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.

View File

@@ -1,100 +1,100 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"Web": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular/build:application",
"options": {
"browser": "src/main.ts",
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
{
"glob": "**/*",
"input": "public"
}
],
"styles": [
"src/styles.scss"
]
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kB",
"maximumError": "1MB"
},
{
"type": "anyComponentStyle",
"maximumWarning": "4kB",
"maximumError": "8kB"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular/build:dev-server",
"options": {
"allowedHosts": [
"localhost",
"4200--0197f0a3-fef1-7776-b920-71d5d9c3d787.us01.gitpod.dev"
]
},
"configurations": {
"production": {
"buildTarget": "Web:build:production"
},
"development": {
"buildTarget": "Web:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular/build:extract-i18n"
},
"test": {
"builder": "@angular/build:karma",
"options": {
"tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss",
"assets": [
{
"glob": "**/*",
"input": "public"
}
],
"styles": [
"src/styles.scss"
]
}
}
}
}
},
"cli": {
"analytics": false
}
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"Web": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular/build:application",
"options": {
"browser": "src/main.ts",
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
{
"glob": "**/*",
"input": "public"
}
],
"styles": [
"src/styles.scss"
]
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kB",
"maximumError": "1MB"
},
{
"type": "anyComponentStyle",
"maximumWarning": "4kB",
"maximumError": "8kB"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular/build:dev-server",
"options": {
"allowedHosts": [
"localhost",
"4200--0197f0a3-fef1-7776-b920-71d5d9c3d787.us01.gitpod.dev"
]
},
"configurations": {
"production": {
"buildTarget": "Web:build:production"
},
"development": {
"buildTarget": "Web:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular/build:extract-i18n"
},
"test": {
"builder": "@angular/build:karma",
"options": {
"tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss",
"assets": [
{
"glob": "**/*",
"input": "public"
}
],
"styles": [
"src/styles.scss"
]
}
}
}
}
},
"cli": {
"analytics": false
}
}

17828
Web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,46 +1,46 @@
{
"name": "web",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve --host 0.0.0.0",
"build": "ng build --configuration=production",
"watch": "ng build --watch --configuration development",
"test": "ng test"
},
"prettier": {
"overrides": [
{
"files": "*.html",
"options": {
"parser": "angular"
}
}
]
},
"private": true,
"dependencies": {
"@angular/common": "^20.0.0",
"@angular/compiler": "^20.0.0",
"@angular/core": "^20.0.0",
"@angular/forms": "^20.0.0",
"@angular/platform-browser": "^20.0.0",
"@angular/router": "^20.0.0",
"angular-oauth2-oidc": "^20.0.2",
"rxjs": "~7.8.0",
"tslib": "^2.3.0"
},
"devDependencies": {
"@angular/build": "^20.0.6",
"@angular/cli": "^20.0.6",
"@angular/compiler-cli": "^20.0.0",
"@types/jasmine": "~5.1.0",
"jasmine-core": "~5.7.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"typescript": "~5.8.2"
}
}
{
"name": "web",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve --host 0.0.0.0",
"build": "ng build --configuration=production",
"watch": "ng build --watch --configuration development",
"test": "ng test"
},
"prettier": {
"overrides": [
{
"files": "*.html",
"options": {
"parser": "angular"
}
}
]
},
"private": true,
"dependencies": {
"@angular/common": "^20.0.0",
"@angular/compiler": "^20.0.0",
"@angular/core": "^20.0.0",
"@angular/forms": "^20.0.0",
"@angular/platform-browser": "^20.0.0",
"@angular/router": "^20.0.0",
"angular-oauth2-oidc": "^20.0.2",
"rxjs": "~7.8.0",
"tslib": "^2.3.0"
},
"devDependencies": {
"@angular/build": "^20.0.6",
"@angular/cli": "^20.0.6",
"@angular/compiler-cli": "^20.0.0",
"@types/jasmine": "~5.1.0",
"jasmine-core": "~5.7.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"typescript": "~5.8.2"
}
}

View File

@@ -1,3 +1,3 @@
{
"apiEndpoint": "https://localhost:5001"
{
"apiEndpoint": "https://localhost:5001"
}

View File

@@ -1,34 +1,34 @@
import { ApplicationConfig, inject, provideAppInitializer, provideBrowserGlobalErrorListeners, provideZonelessChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { DefaultOAuthInterceptor, provideOAuthClient } from 'angular-oauth2-oidc';
import { AppConfigService } from './services/config.service';
import { ApiEndpointInterceptor } from './services/http.interceptor';
export const appConfig: ApplicationConfig = {
providers: [
provideBrowserGlobalErrorListeners(),
provideZonelessChangeDetection(),
provideRouter(routes),
provideAppInitializer(() => inject(AppConfigService).loadConfig()),
{
provide: HTTP_INTERCEPTORS,
useClass: DefaultOAuthInterceptor,
multi: true,
},
{
provide: HTTP_INTERCEPTORS,
useClass: ApiEndpointInterceptor,
multi: true,
},
provideHttpClient(withInterceptorsFromDi()),
provideOAuthClient({
resourceServer: {
allowedUrls: ['http://localhost:5000', 'https://localhost:5001', 'https://centrum.lesko.me','https://beta.e-dias.sk/'],
sendAccessToken: true,
},
}),
]
};
import { ApplicationConfig, inject, provideAppInitializer, provideBrowserGlobalErrorListeners, provideZonelessChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { DefaultOAuthInterceptor, provideOAuthClient } from 'angular-oauth2-oidc';
import { AppConfigService } from './services/config.service';
import { ApiEndpointInterceptor } from './services/http.interceptor';
export const appConfig: ApplicationConfig = {
providers: [
provideBrowserGlobalErrorListeners(),
provideZonelessChangeDetection(),
provideRouter(routes),
provideAppInitializer(() => inject(AppConfigService).loadConfig()),
{
provide: HTTP_INTERCEPTORS,
useClass: DefaultOAuthInterceptor,
multi: true,
},
{
provide: HTTP_INTERCEPTORS,
useClass: ApiEndpointInterceptor,
multi: true,
},
provideHttpClient(withInterceptorsFromDi()),
provideOAuthClient({
resourceServer: {
allowedUrls: ['http://localhost:5000', 'https://localhost:5001', 'https://centrum.lesko.me','https://beta.e-dias.sk/'],
sendAccessToken: true,
},
}),
]
};

View File

@@ -1 +1 @@
<router-outlet />
<router-outlet />

View File

@@ -1,9 +1,9 @@
import { CanActivateFn } from '@angular/router';
import { inject } from '@angular/core';
import { OAuthService } from 'angular-oauth2-oidc';
export const authGuard: CanActivateFn = (route, state) => {
const authService = inject(OAuthService);
return authService.hasValidAccessToken(); // returns boolean, Promise, or Observable
};
import { CanActivateFn } from '@angular/router';
import { inject } from '@angular/core';
import { OAuthService } from 'angular-oauth2-oidc';
export const authGuard: CanActivateFn = (route, state) => {
const authService = inject(OAuthService);
return authService.hasValidAccessToken(); // returns boolean, Promise, or Observable
};

View File

@@ -1,20 +1,20 @@
import { Routes } from '@angular/router';
import { Login } from './login/login';
import { authGuard } from './app.route.guard';
export const routes: Routes = [
{
path: 'login',
component: Login,
},
{
path: 'content',
children: [
{
path: '',
loadComponent: () => import('./content/content').then(m => m.Content),
canActivate: [authGuard],
}
],
}
];
import { Routes } from '@angular/router';
import { Login } from './login/login';
import { authGuard } from './app.route.guard';
export const routes: Routes = [
{
path: 'login',
component: Login,
},
{
path: 'content',
children: [
{
path: '',
loadComponent: () => import('./content/content').then(m => m.Content),
canActivate: [authGuard],
}
],
}
];

View File

@@ -1,25 +1,25 @@
import { provideZonelessChangeDetection } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { App } from './app';
describe('App', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [App],
providers: [provideZonelessChangeDetection()]
}).compileComponents();
});
it('should create the app', () => {
const fixture = TestBed.createComponent(App);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it('should render title', () => {
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, Web');
});
});
import { provideZonelessChangeDetection } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { App } from './app';
describe('App', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [App],
providers: [provideZonelessChangeDetection()]
}).compileComponents();
});
it('should create the app', () => {
const fixture = TestBed.createComponent(App);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it('should render title', () => {
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, Web');
});
});

View File

@@ -1,31 +1,31 @@
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { APP_INITIALIZER, Component, inject, OnInit, provideAppInitializer } from '@angular/core';
import { Router, RouterOutlet } from '@angular/router';
import { DefaultOAuthInterceptor, OAuthService } from 'angular-oauth2-oidc';
import { AppConfigService } from './services/config.service';
@Component({
selector: 'app-root',
imports: [RouterOutlet],
providers: [
OAuthService,
],
templateUrl: './app.html',
styleUrl: './app.scss'
})
export class App implements OnInit {
protected title = 'Web';
constructor(private readonly as: OAuthService, private readonly router: Router) {
this.as.configure({
issuer: 'https://identity.lesko.me',
redirectUri: window.location.origin + '/login',
clientId: '21131567-fea1-42a2-8907-21abd874eff8',
scope: 'openid profile email',
responseType: 'code',
timeoutFactor: 0.01,
});
}
ngOnInit(): void {
this.as.loadDiscoveryDocumentAndLogin().then(() => this.router.navigate(['login']));
}
}
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { APP_INITIALIZER, Component, inject, OnInit, provideAppInitializer } from '@angular/core';
import { Router, RouterOutlet } from '@angular/router';
import { DefaultOAuthInterceptor, OAuthService } from 'angular-oauth2-oidc';
import { AppConfigService } from './services/config.service';
@Component({
selector: 'app-root',
imports: [RouterOutlet],
providers: [
OAuthService,
],
templateUrl: './app.html',
styleUrl: './app.scss'
})
export class App implements OnInit {
protected title = 'Web';
constructor(private readonly as: OAuthService, private readonly router: Router) {
this.as.configure({
issuer: 'https://identity.lesko.me',
redirectUri: window.location.origin + '/login',
clientId: '21131567-fea1-42a2-8907-21abd874eff8',
scope: 'openid profile email',
responseType: 'code',
timeoutFactor: 0.01,
});
}
ngOnInit(): void {
this.as.loadDiscoveryDocumentAndLogin().then(() => this.router.navigate(['login']));
}
}

View File

@@ -1,23 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Content } from './content';
describe('Content', () => {
let component: Content;
let fixture: ComponentFixture<Content>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [Content]
})
.compileComponents();
fixture = TestBed.createComponent(Content);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Content } from './content';
describe('Content', () => {
let component: Content;
let fixture: ComponentFixture<Content>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [Content]
})
.compileComponents();
fixture = TestBed.createComponent(Content);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -1,22 +1,22 @@
import { JsonPipe } from '@angular/common';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Component, isDevMode, signal } from '@angular/core';
import { OAuthService } from 'angular-oauth2-oidc';
import { AppConfigService } from '../services/config.service';
@Component({
selector: 'app-content',
imports: [JsonPipe],
templateUrl: './content.html',
styleUrl: './content.scss'
})
export class Content {
data = signal({});
constructor(httpClient: HttpClient, readonly as: OAuthService, readonly cs: AppConfigService) {
httpClient.get('/api/product')
.subscribe(data => {
this.data.set(data);
});
}
}
import { JsonPipe } from '@angular/common';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Component, isDevMode, signal } from '@angular/core';
import { OAuthService } from 'angular-oauth2-oidc';
import { AppConfigService } from '../services/config.service';
@Component({
selector: 'app-content',
imports: [JsonPipe],
templateUrl: './content.html',
styleUrl: './content.scss'
})
export class Content {
data = signal({});
constructor(httpClient: HttpClient, readonly as: OAuthService, readonly cs: AppConfigService) {
httpClient.get('/api/product')
.subscribe(data => {
this.data.set(data);
});
}
}

View File

@@ -1,2 +1,2 @@
Logging in...

View File

@@ -1,23 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Login } from './login';
describe('Login', () => {
let component: Login;
let fixture: ComponentFixture<Login>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [Login]
})
.compileComponents();
fixture = TestBed.createComponent(Login);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Login } from './login';
describe('Login', () => {
let component: Login;
let fixture: ComponentFixture<Login>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [Login]
})
.compileComponents();
fixture = TestBed.createComponent(Login);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -1,33 +1,33 @@
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { OAuthService } from 'angular-oauth2-oidc';
@Component({
selector: 'app-login',
templateUrl: './login.html',
styleUrl: './login.scss'
})
export class Login implements OnInit {
constructor(readonly as: OAuthService, readonly router: Router) { }
ngOnInit(): void {
if (this.as.hasValidAccessToken() && this.as.hasValidIdToken()) {
this.getUserInfo();
} else {
this.as.events.subscribe(event => {
if (event.type === 'token_received')
if (this.as.hasValidIdToken()) {
this.getUserInfo();
}
})
}
}
getUserInfo(): void {
this.as.loadUserProfile().then(value => {
console.log('User profile loaded:', value);
this.router.navigate(['/content']);
});
}
}
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { OAuthService } from 'angular-oauth2-oidc';
@Component({
selector: 'app-login',
templateUrl: './login.html',
styleUrl: './login.scss'
})
export class Login implements OnInit {
constructor(readonly as: OAuthService, readonly router: Router) { }
ngOnInit(): void {
if (this.as.hasValidAccessToken() && this.as.hasValidIdToken()) {
this.getUserInfo();
} else {
this.as.events.subscribe(event => {
if (event.type === 'token_received')
if (this.as.hasValidIdToken()) {
this.getUserInfo();
}
})
}
}
getUserInfo(): void {
this.as.loadUserProfile().then(value => {
console.log('User profile loaded:', value);
this.router.navigate(['/content']);
});
}
}

View File

@@ -1,31 +1,31 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class AppConfigService {
private config: any;
public loaded: boolean = false;
constructor() { }
loadConfig(): Observable<any> {
return new Observable(observer => {
fetch('/config.json')
.then(async response => {
if (!response.ok) {
throw new Error(`Could not load config.json: ${response.statusText}`);
}
this.config = await response.json();
this.loaded = true;
observer.next(this.config);
observer.complete();
})
.catch(error => observer.error(error));
});
}
get setting() {
return this.config;
}
}
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class AppConfigService {
private config: any;
public loaded: boolean = false;
constructor() { }
loadConfig(): Observable<any> {
return new Observable(observer => {
fetch('/config.json')
.then(async response => {
if (!response.ok) {
throw new Error(`Could not load config.json: ${response.statusText}`);
}
this.config = await response.json();
this.loaded = true;
observer.next(this.config);
observer.complete();
})
.catch(error => observer.error(error));
});
}
get setting() {
return this.config;
}
}

View File

@@ -1,25 +1,25 @@
import { Injectable } from '@angular/core';
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs';
import { AppConfigService } from './config.service';
@Injectable({ providedIn: 'root' })
export class ApiEndpointInterceptor implements HttpInterceptor {
constructor(private readonly configService: AppConfigService) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return this.prependUrl(req, next);
}
prependUrl(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const apiEndpoint = this.configService.setting.apiEndpoint;
if (apiEndpoint === undefined || !this.configService.loaded)
throw new Error('API endpoint is not defined or config not loaded');
const isRelative = !/^https?:\/\//i.test(req.url);
const url = isRelative ? `${apiEndpoint.replace(/\/$/, '')}/${req.url.replace(/^\//, '')}` : req.url;
const cloned = req.clone({ url });
console.trace('ApiEndpointInterceptor hit:', url);
return next.handle(cloned);
}
}
import { Injectable } from '@angular/core';
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs';
import { AppConfigService } from './config.service';
@Injectable({ providedIn: 'root' })
export class ApiEndpointInterceptor implements HttpInterceptor {
constructor(private readonly configService: AppConfigService) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return this.prependUrl(req, next);
}
prependUrl(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const apiEndpoint = this.configService.setting.apiEndpoint;
if (apiEndpoint === undefined || !this.configService.loaded)
throw new Error('API endpoint is not defined or config not loaded');
const isRelative = !/^https?:\/\//i.test(req.url);
const url = isRelative ? `${apiEndpoint.replace(/\/$/, '')}/${req.url.replace(/^\//, '')}` : req.url;
const cloned = req.clone({ url });
console.trace('ApiEndpointInterceptor hit:', url);
return next.handle(cloned);
}
}

View File

@@ -1,13 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Web</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Web</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>

View File

@@ -1,6 +1,6 @@
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { App } from './app/app';
bootstrapApplication(App, appConfig)
.catch((err) => console.error(err));
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { App } from './app/app';
bootstrapApplication(App, appConfig)
.catch((err) => console.error(err));

View File

@@ -1 +1 @@
/* You can add global styles to this file, and also import other style files */
/* You can add global styles to this file, and also import other style files */

View File

@@ -1,16 +1,16 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
[*.md]
max_line_length = off
trim_trailing_whitespace = false
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
[*.md]
max_line_length = off
trim_trailing_whitespace = false

View File

@@ -1,42 +1,42 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# Compiled output
/dist
/tmp
/out-tsc
/bazel-out
# Node
/node_modules
npm-debug.log
yarn-error.log
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
# System files
.DS_Store
Thumbs.db
# See http://help.github.com/ignore-files/ for more about ignoring files.
# Compiled output
/dist
/tmp
/out-tsc
/bazel-out
# Node
/node_modules
npm-debug.log
yarn-error.log
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
# System files
.DS_Store
Thumbs.db

View File

@@ -1,2 +1,2 @@
# Modernize-Angular-pro
Modernize Angular Admin Dashboard
# Modernize-Angular-pro
Modernize Angular Admin Dashboard

View File

@@ -1,96 +1,96 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"Modernize": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"allowedCommonJsDependencies": ["apexcharts", "bezier-easing", "moment"],
"outputPath": "dist/Modernize",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": ["zone.js"],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": ["src/favicon.ico", "src/assets"],
"styles": ["src/styles.scss", "src/assets/scss/style.scss"],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "12mb",
"maximumError": "12mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "12mb",
"maximumError": "12mb"
}
],
"outputHashing": "all"
},
"development": {
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "Modernize:build:production"
},
"hmr": {
"hmr": true
},
"development": {
"buildTarget": "Modernize:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "Modernize:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"polyfills": ["zone.js", "zone.js/testing"],
"tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss",
"assets": ["src/favicon.ico", "src/assets"],
"styles": ["src/styles.scss"],
"scripts": []
}
}
}
}
},
"cli": {
"analytics": false
}
}
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"Modernize": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"allowedCommonJsDependencies": ["apexcharts", "bezier-easing", "moment"],
"outputPath": "dist/Modernize",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": ["zone.js"],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": ["src/favicon.ico", "src/assets"],
"styles": ["src/styles.scss", "src/assets/scss/style.scss"],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "12mb",
"maximumError": "12mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "12mb",
"maximumError": "12mb"
}
],
"outputHashing": "all"
},
"development": {
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "Modernize:build:production"
},
"hmr": {
"hmr": true
},
"development": {
"buildTarget": "Modernize:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "Modernize:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"polyfills": ["zone.js", "zone.js/testing"],
"tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss",
"assets": ["src/favicon.ico", "src/assets"],
"styles": ["src/styles.scss"],
"scripts": []
}
}
}
}
},
"cli": {
"analytics": false
}
}

View File

@@ -1,4 +1,4 @@
[[redirects]]
from = "/*"
to = "/index.html"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200

File diff suppressed because it is too large Load Diff

View File

@@ -1,48 +1,48 @@
{
"name": "modernize",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test"
},
"private": true,
"dependencies": {
"@angular/animations": "^19.0.0",
"@angular/cdk": "^19.0.0",
"@angular/common": "^19.0.0",
"@angular/compiler": "^19.0.0",
"@angular/core": "^19.0.0",
"@angular/forms": "^19.0.0",
"@angular/material": "^19.0.0",
"@angular/platform-browser": "^19.0.0",
"@angular/platform-browser-dynamic": "^19.0.0",
"@angular/router": "^19.0.0",
"@ngx-translate/core": "^14.0.0",
"@ngx-translate/http-loader": "^7.0.0",
"angular-tabler-icons": "^2.7.0",
"apexcharts": "^3.49.0",
"ng-apexcharts": "1.7.6",
"ngx-scrollbar": "^11.0.0",
"rxjs": "~7.5.0",
"sass": "1.81.0",
"tslib": "^2.3.0",
"zone.js": "~0.15.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "^19.0.1",
"@angular/cli": "~19.0.1",
"@angular/compiler-cli": "^19.0.0",
"@types/date-fns": "^2.6.0",
"@types/jasmine": "~4.3.0",
"jasmine-core": "~4.5.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.0.0",
"typescript": "~5.6.3"
}
}
{
"name": "modernize",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test"
},
"private": true,
"dependencies": {
"@angular/animations": "^19.0.0",
"@angular/cdk": "^19.0.0",
"@angular/common": "^19.0.0",
"@angular/compiler": "^19.0.0",
"@angular/core": "^19.0.0",
"@angular/forms": "^19.0.0",
"@angular/material": "^19.0.0",
"@angular/platform-browser": "^19.0.0",
"@angular/platform-browser-dynamic": "^19.0.0",
"@angular/router": "^19.0.0",
"@ngx-translate/core": "^14.0.0",
"@ngx-translate/http-loader": "^7.0.0",
"angular-tabler-icons": "^2.7.0",
"apexcharts": "^3.49.0",
"ng-apexcharts": "1.7.6",
"ngx-scrollbar": "^11.0.0",
"rxjs": "~7.5.0",
"sass": "1.81.0",
"tslib": "^2.3.0",
"zone.js": "~0.15.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "^19.0.1",
"@angular/cli": "~19.0.1",
"@angular/compiler-cli": "^19.0.0",
"@types/date-fns": "^2.6.0",
"@types/jasmine": "~4.3.0",
"jasmine-core": "~4.5.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.0.0",
"typescript": "~5.6.3"
}
}

View File

@@ -1 +1 @@
<router-outlet></router-outlet>
<router-outlet></router-outlet>

View File

@@ -1,35 +1,35 @@
import { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
RouterTestingModule
],
declarations: [
AppComponent
],
}).compileComponents();
});
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it(`should have as title 'Angular15'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app.title).toEqual('Angular15');
});
it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('.content span')?.textContent).toContain('Angular15 app is running!');
});
});
import { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
RouterTestingModule
],
declarations: [
AppComponent
],
}).compileComponents();
});
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it(`should have as title 'Angular15'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app.title).toEqual('Angular15');
});
it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('.content span')?.textContent).toContain('Angular15 app is running!');
});
});

View File

@@ -1,11 +1,11 @@
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
@Component({
selector: 'app-root',
imports: [RouterOutlet],
templateUrl: './app.component.html'
})
export class AppComponent {
title = 'Modernize Angular Admin Template';
}
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
@Component({
selector: 'app-root',
imports: [RouterOutlet],
templateUrl: './app.component.html'
})
export class AppComponent {
title = 'Modernize Angular Admin Template';
}

View File

@@ -1,55 +1,55 @@
import {
ApplicationConfig,
provideZoneChangeDetection,
importProvidersFrom,
} from '@angular/core';
import {
HttpClient,
provideHttpClient,
withInterceptorsFromDi,
} from '@angular/common/http';
import { routes } from './app.routes';
import {
provideRouter,
withComponentInputBinding,
withInMemoryScrolling,
} from '@angular/router';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { provideClientHydration } from '@angular/platform-browser';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
// icons
import { TablerIconsModule } from 'angular-tabler-icons';
import * as TablerIcons from 'angular-tabler-icons/icons';
// perfect scrollbar
import { NgScrollbarModule } from 'ngx-scrollbar';
//Import all material modules
import { MaterialModule } from './material.module';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(
routes,
withInMemoryScrolling({
scrollPositionRestoration: 'enabled',
anchorScrolling: 'enabled',
}),
withComponentInputBinding()
),
provideHttpClient(withInterceptorsFromDi()),
provideClientHydration(),
provideAnimationsAsync(),
importProvidersFrom(
FormsModule,
ReactiveFormsModule,
MaterialModule,
TablerIconsModule.pick(TablerIcons),
NgScrollbarModule,
),
],
};
import {
ApplicationConfig,
provideZoneChangeDetection,
importProvidersFrom,
} from '@angular/core';
import {
HttpClient,
provideHttpClient,
withInterceptorsFromDi,
} from '@angular/common/http';
import { routes } from './app.routes';
import {
provideRouter,
withComponentInputBinding,
withInMemoryScrolling,
} from '@angular/router';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { provideClientHydration } from '@angular/platform-browser';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
// icons
import { TablerIconsModule } from 'angular-tabler-icons';
import * as TablerIcons from 'angular-tabler-icons/icons';
// perfect scrollbar
import { NgScrollbarModule } from 'ngx-scrollbar';
//Import all material modules
import { MaterialModule } from './material.module';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(
routes,
withInMemoryScrolling({
scrollPositionRestoration: 'enabled',
anchorScrolling: 'enabled',
}),
withComponentInputBinding()
),
provideHttpClient(withInterceptorsFromDi()),
provideClientHydration(),
provideAnimationsAsync(),
importProvidersFrom(
FormsModule,
ReactiveFormsModule,
MaterialModule,
TablerIconsModule.pick(TablerIcons),
NgScrollbarModule,
),
],
};

View File

@@ -1,51 +1,51 @@
import { Routes } from '@angular/router';
import { BlankComponent } from './layouts/blank/blank.component';
import { FullComponent } from './layouts/full/full.component';
export const routes: Routes = [
{
path: '',
component: FullComponent,
children: [
{
path: '',
redirectTo: '/dashboard',
pathMatch: 'full',
},
{
path: 'dashboard',
loadChildren: () =>
import('./pages/pages.routes').then((m) => m.PagesRoutes),
},
{
path: 'ui-components',
loadChildren: () =>
import('./pages/ui-components/ui-components.routes').then(
(m) => m.UiComponentsRoutes
),
},
{
path: 'extra',
loadChildren: () =>
import('./pages/extra/extra.routes').then((m) => m.ExtraRoutes),
},
],
},
{
path: '',
component: BlankComponent,
children: [
{
path: 'authentication',
loadChildren: () =>
import('./pages/authentication/authentication.routes').then(
(m) => m.AuthenticationRoutes
),
},
],
},
{
path: '**',
redirectTo: 'authentication/error',
},
];
import { Routes } from '@angular/router';
import { BlankComponent } from './layouts/blank/blank.component';
import { FullComponent } from './layouts/full/full.component';
export const routes: Routes = [
{
path: '',
component: FullComponent,
children: [
{
path: '',
redirectTo: '/dashboard',
pathMatch: 'full',
},
{
path: 'dashboard',
loadChildren: () =>
import('./pages/pages.routes').then((m) => m.PagesRoutes),
},
{
path: 'ui-components',
loadChildren: () =>
import('./pages/ui-components/ui-components.routes').then(
(m) => m.UiComponentsRoutes
),
},
{
path: 'extra',
loadChildren: () =>
import('./pages/extra/extra.routes').then((m) => m.ExtraRoutes),
},
],
},
{
path: '',
component: BlankComponent,
children: [
{
path: 'authentication',
loadChildren: () =>
import('./pages/authentication/authentication.routes').then(
(m) => m.AuthenticationRoutes
),
},
],
},
{
path: '**',
redirectTo: 'authentication/error',
},
];

View File

@@ -1,32 +1,32 @@
<div class="row">
@for(productcard of productcards; track productcards) {
<div class="col-sm-6 col-lg-3">
<mat-card class="cardWithShadow productcard overflow-hidden">
<a routerLink="/widgets/cards">
<img src="{{ productcard.imgSrc }}" alt="imgSrc" class="w-100" mat-card-image />
</a>
<mat-card-content class="p-b-24 p-t-12 position-relative">
<button mat-mini-fab class="icon-30 cart-btn bg-primary text-white d-block" matTooltip="Add to Cart">
<i-tabler name="basket" class="icon-16"></i-tabler>
</button>
<mat-card-title class="mat-headline-2 f-s-16 m-b-4">{{
productcard.title
}}</mat-card-title>
<div class="d-flex align-items-center justify-content-between">
<div class="d-flex align-items-center">
<h6 class="f-w-600 f-s-16">${{ productcard.price }}</h6>
<span class="f-s-14 m-l-4 text-decoration-line-through">${{ productcard.rprice }}</span>
</div>
<div class="m-l-auto d-flex gap-4">
<span><i-tabler name="star" class="fill-warning icon-18"></i-tabler></span>
<span><i-tabler name="star" class="fill-warning icon-18"></i-tabler></span>
<span><i-tabler name="star" class="fill-warning icon-18"></i-tabler></span>
<span><i-tabler name="star" class="fill-warning icon-18"></i-tabler></span>
<span><i-tabler name="star" class="fill-warning icon-18"></i-tabler></span>
</div>
</div>
</mat-card-content>
</mat-card>
</div>
}
<div class="row">
@for(productcard of productcards; track productcards) {
<div class="col-sm-6 col-lg-3">
<mat-card class="cardWithShadow productcard overflow-hidden">
<a routerLink="/widgets/cards">
<img src="{{ productcard.imgSrc }}" alt="imgSrc" class="w-100" mat-card-image />
</a>
<mat-card-content class="p-b-24 p-t-12 position-relative">
<button mat-mini-fab class="icon-30 cart-btn bg-primary text-white d-block" matTooltip="Add to Cart">
<i-tabler name="basket" class="icon-16"></i-tabler>
</button>
<mat-card-title class="mat-headline-2 f-s-16 m-b-4">{{
productcard.title
}}</mat-card-title>
<div class="d-flex align-items-center justify-content-between">
<div class="d-flex align-items-center">
<h6 class="f-w-600 f-s-16">${{ productcard.price }}</h6>
<span class="f-s-14 m-l-4 text-decoration-line-through">${{ productcard.rprice }}</span>
</div>
<div class="m-l-auto d-flex gap-4">
<span><i-tabler name="star" class="fill-warning icon-18"></i-tabler></span>
<span><i-tabler name="star" class="fill-warning icon-18"></i-tabler></span>
<span><i-tabler name="star" class="fill-warning icon-18"></i-tabler></span>
<span><i-tabler name="star" class="fill-warning icon-18"></i-tabler></span>
<span><i-tabler name="star" class="fill-warning icon-18"></i-tabler></span>
</div>
</div>
</mat-card-content>
</mat-card>
</div>
}
</div>

View File

@@ -1,53 +1,53 @@
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { TablerIconsModule } from 'angular-tabler-icons';
// ecommerce card
interface productCards {
id: number;
imgSrc: string;
title: string;
price: string;
rprice: string;
}
@Component({
selector: 'app-blog-card',
imports: [MatCardModule, TablerIconsModule, MatButtonModule],
templateUrl: './blog-card.component.html',
})
export class AppBlogCardsComponent {
constructor() { }
productcards: productCards[] = [
{
id: 1,
imgSrc: '/assets/images/products/s4.jpg',
title: 'Boat Headphone',
price: '285',
rprice: '375',
},
{
id: 2,
imgSrc: '/assets/images/products/s5.jpg',
title: 'MacBook Air Pro',
price: '285',
rprice: '375',
},
{
id: 3,
imgSrc: '/assets/images/products/s7.jpg',
title: 'Red Valvet Dress',
price: '285',
rprice: '375',
},
{
id: 4,
imgSrc: '/assets/images/products/s11.jpg',
title: 'Cute Soft Teddybear',
price: '285',
rprice: '375',
},
];
}
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { TablerIconsModule } from 'angular-tabler-icons';
// ecommerce card
interface productCards {
id: number;
imgSrc: string;
title: string;
price: string;
rprice: string;
}
@Component({
selector: 'app-blog-card',
imports: [MatCardModule, TablerIconsModule, MatButtonModule],
templateUrl: './blog-card.component.html',
})
export class AppBlogCardsComponent {
constructor() { }
productcards: productCards[] = [
{
id: 1,
imgSrc: '/assets/images/products/s4.jpg',
title: 'Boat Headphone',
price: '285',
rprice: '375',
},
{
id: 2,
imgSrc: '/assets/images/products/s5.jpg',
title: 'MacBook Air Pro',
price: '285',
rprice: '375',
},
{
id: 3,
imgSrc: '/assets/images/products/s7.jpg',
title: 'Red Valvet Dress',
price: '285',
rprice: '375',
},
{
id: 4,
imgSrc: '/assets/images/products/s11.jpg',
title: 'Cute Soft Teddybear',
price: '285',
rprice: '375',
},
];
}

View File

@@ -1,24 +1,24 @@
<mat-card class="cardWithShadow overflow-hidden">
<mat-card-content class="p-b-0">
<div class="d-flex align-items-center m-b-8">
<mat-card-title>Monthly Earnings</mat-card-title>
<div class="m-l-auto">
<button mat-fab class="icon-48 bg-secondary ">
<i-tabler name="currency-dollar" class="text-white d-flex"></i-tabler>
</button>
</div>
</div>
<h4 class="f-s-24 m-b-6">$6,820</h4>
<div class="d-flex align-items-center m-t-16">
<button mat-mini-fab class="bg-light-error text-error shadow-none icon-27 p-0 d-flex align-items-center justify-content-center">
<i-tabler name="arrow-down-right" class="icon-20 d-flex align-items-center"></i-tabler>
</button>
<div class="f-s-14 f-w-600 m-l-12">+9%</div>
<div class="f-s-14 m-l-4">last year</div>
</div>
</mat-card-content>
<apx-chart [series]="monthlyChart.series" [dataLabels]="monthlyChart.dataLabels" [chart]="monthlyChart.chart"
[legend]="monthlyChart.legend" [colors]="monthlyChart.colors" [stroke]="monthlyChart.stroke"
[tooltip]="monthlyChart.tooltip" [plotOptions]="monthlyChart.plotOptions"
[responsive]="monthlyChart.responsive"></apx-chart>
<mat-card class="cardWithShadow overflow-hidden">
<mat-card-content class="p-b-0">
<div class="d-flex align-items-center m-b-8">
<mat-card-title>Monthly Earnings</mat-card-title>
<div class="m-l-auto">
<button mat-fab class="icon-48 bg-secondary ">
<i-tabler name="currency-dollar" class="text-white d-flex"></i-tabler>
</button>
</div>
</div>
<h4 class="f-s-24 m-b-6">$6,820</h4>
<div class="d-flex align-items-center m-t-16">
<button mat-mini-fab class="bg-light-error text-error shadow-none icon-27 p-0 d-flex align-items-center justify-content-center">
<i-tabler name="arrow-down-right" class="icon-20 d-flex align-items-center"></i-tabler>
</button>
<div class="f-s-14 f-w-600 m-l-12">+9%</div>
<div class="f-s-14 m-l-4">last year</div>
</div>
</mat-card-content>
<apx-chart [series]="monthlyChart.series" [dataLabels]="monthlyChart.dataLabels" [chart]="monthlyChart.chart"
[legend]="monthlyChart.legend" [colors]="monthlyChart.colors" [stroke]="monthlyChart.stroke"
[tooltip]="monthlyChart.tooltip" [plotOptions]="monthlyChart.plotOptions"
[responsive]="monthlyChart.responsive"></apx-chart>
</mat-card>

View File

@@ -1,81 +1,81 @@
import { Component, ViewChild } from '@angular/core';
import {
ApexChart,
ChartComponent,
ApexDataLabels,
ApexLegend,
ApexStroke,
ApexTooltip,
ApexAxisChartSeries,
ApexPlotOptions,
ApexResponsive,
NgApexchartsModule,
} from 'ng-apexcharts';
import { TablerIconsModule } from 'angular-tabler-icons';
import { MaterialModule } from 'src/app/material.module';
export interface monthlyChart {
series: ApexAxisChartSeries;
chart: ApexChart;
dataLabels: ApexDataLabels;
plotOptions: ApexPlotOptions;
tooltip: ApexTooltip;
stroke: ApexStroke;
legend: ApexLegend;
responsive: ApexResponsive;
}
@Component({
selector: 'app-monthly-earnings',
imports: [NgApexchartsModule, MaterialModule, TablerIconsModule],
templateUrl: './monthly-earnings.component.html',
})
export class AppMonthlyEarningsComponent {
@ViewChild('chart') chart: ChartComponent = Object.create(null);
public monthlyChart!: Partial<monthlyChart> | any;
constructor() {
this.monthlyChart = {
series: [
{
name: '',
color: '#49BEFF',
data: [25, 66, 20, 40, 12, 58, 20],
},
],
chart: {
type: 'area',
fontFamily: "'Plus Jakarta Sans', sans-serif;",
foreColor: '#adb0bb',
toolbar: {
show: false,
},
height: 85,
sparkline: {
enabled: true,
},
group: 'sparklines',
},
stroke: {
curve: 'smooth',
width: 2,
},
fill: {
colors: ['#E8F7FF'],
type: 'solid',
opacity: 0.05,
},
markers: {
size: 0,
},
tooltip: {
theme: 'dark',
x: {
show: false,
},
},
};
}
}
import { Component, ViewChild } from '@angular/core';
import {
ApexChart,
ChartComponent,
ApexDataLabels,
ApexLegend,
ApexStroke,
ApexTooltip,
ApexAxisChartSeries,
ApexPlotOptions,
ApexResponsive,
NgApexchartsModule,
} from 'ng-apexcharts';
import { TablerIconsModule } from 'angular-tabler-icons';
import { MaterialModule } from 'src/app/material.module';
export interface monthlyChart {
series: ApexAxisChartSeries;
chart: ApexChart;
dataLabels: ApexDataLabels;
plotOptions: ApexPlotOptions;
tooltip: ApexTooltip;
stroke: ApexStroke;
legend: ApexLegend;
responsive: ApexResponsive;
}
@Component({
selector: 'app-monthly-earnings',
imports: [NgApexchartsModule, MaterialModule, TablerIconsModule],
templateUrl: './monthly-earnings.component.html',
})
export class AppMonthlyEarningsComponent {
@ViewChild('chart') chart: ChartComponent = Object.create(null);
public monthlyChart!: Partial<monthlyChart> | any;
constructor() {
this.monthlyChart = {
series: [
{
name: '',
color: '#49BEFF',
data: [25, 66, 20, 40, 12, 58, 20],
},
],
chart: {
type: 'area',
fontFamily: "'Plus Jakarta Sans', sans-serif;",
foreColor: '#adb0bb',
toolbar: {
show: false,
},
height: 85,
sparkline: {
enabled: true,
},
group: 'sparklines',
},
stroke: {
curve: 'smooth',
width: 2,
},
fill: {
colors: ['#E8F7FF'],
type: 'solid',
opacity: 0.05,
},
markers: {
size: 0,
},
tooltip: {
theme: 'dark',
x: {
show: false,
},
},
};
}
}

View File

@@ -1,98 +1,98 @@
<mat-card class="cardWithShadow">
<mat-card-content>
<div class="d-flex">
<div class="m-r-auto">
<mat-card-title>Top Projects</mat-card-title>
<mat-card-subtitle>Best Products</mat-card-subtitle>
</div>
<mat-form-field class="theme-select" appearance="outline">
<mat-select value="mar">
@for(month of months; track month.value) {
<mat-option [value]="month.value">
{{ month.viewValue }}
</mat-option>
}
</mat-select>
</mat-form-field>
</div>
<div class="table-responsive">
<table mat-table [dataSource]="dataSource" class="w-100">
<!-- Position Column -->
<ng-container matColumnDef="product">
<th mat-header-cell *matHeaderCellDef class="f-w-600 f-s-14 p-l-0">
Product
</th>
<td mat-cell *matCellDef="let element" class="p-l-0">
<div class="d-flex align-items-center">
<img [src]="element.imagePath" alt="users" width="48" class="rounded" />
<div class="m-l-16">
<h6 class="f-s-14 f-w-600">
{{ element.pname }}
</h6>
<span class=" f-s-12">
{{ element.category }}
</span>
</div>
</div>
</td>
</ng-container>
<!-- Name Column -->
<ng-container matColumnDef="progress">
<th mat-header-cell *matHeaderCellDef class="f-w-600 f-s-14">
Progress
</th>
<td mat-cell *matCellDef="let element">
{{ element.progress }}%
</td>
</ng-container>
<!-- Weight Column -->
<ng-container matColumnDef="status">
<th mat-header-cell *matHeaderCellDef class="f-w-600 f-s-14">
Status
</th>
<td mat-cell *matCellDef="let element">
@if(element.status == 'low') {
<span class="bg-light-success text-success rounded f-w-600 p-6 p-y-4 f-s-12">
{{ element.status | titlecase }}
</span>
}
@if(element.status == 'medium') {
<span class="bg-light-warning text-warning rounded f-w-600 p-6 p-y-4 f-s-12">
{{ element.status | titlecase }}
</span>
}
@if(element.status == 'high') {
<span class="bg-light-primary text-primary rounded f-w-600 p-6 p-y-4 f-s-12">
{{ element.status | titlecase }}
</span>
}
@if(element.status == 'critical') {
<span class="bg-light-error text-error rounded f-w-600 p-6 p-y-4 f-s-12">
{{ element.status | titlecase }}
</span>
}
</td>
</ng-container>
<!-- Symbol Column -->
<ng-container matColumnDef="sales">
<th mat-header-cell *matHeaderCellDef class="f-w-600 f-s-14">
Sales
</th>
<td mat-cell *matCellDef="let element">
${{ element.sales }}k
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
</div>
</mat-card-content>
<mat-card class="cardWithShadow">
<mat-card-content>
<div class="d-flex">
<div class="m-r-auto">
<mat-card-title>Top Projects</mat-card-title>
<mat-card-subtitle>Best Products</mat-card-subtitle>
</div>
<mat-form-field class="theme-select" appearance="outline">
<mat-select value="mar">
@for(month of months; track month.value) {
<mat-option [value]="month.value">
{{ month.viewValue }}
</mat-option>
}
</mat-select>
</mat-form-field>
</div>
<div class="table-responsive">
<table mat-table [dataSource]="dataSource" class="w-100">
<!-- Position Column -->
<ng-container matColumnDef="product">
<th mat-header-cell *matHeaderCellDef class="f-w-600 f-s-14 p-l-0">
Product
</th>
<td mat-cell *matCellDef="let element" class="p-l-0">
<div class="d-flex align-items-center">
<img [src]="element.imagePath" alt="users" width="48" class="rounded" />
<div class="m-l-16">
<h6 class="f-s-14 f-w-600">
{{ element.pname }}
</h6>
<span class=" f-s-12">
{{ element.category }}
</span>
</div>
</div>
</td>
</ng-container>
<!-- Name Column -->
<ng-container matColumnDef="progress">
<th mat-header-cell *matHeaderCellDef class="f-w-600 f-s-14">
Progress
</th>
<td mat-cell *matCellDef="let element">
{{ element.progress }}%
</td>
</ng-container>
<!-- Weight Column -->
<ng-container matColumnDef="status">
<th mat-header-cell *matHeaderCellDef class="f-w-600 f-s-14">
Status
</th>
<td mat-cell *matCellDef="let element">
@if(element.status == 'low') {
<span class="bg-light-success text-success rounded f-w-600 p-6 p-y-4 f-s-12">
{{ element.status | titlecase }}
</span>
}
@if(element.status == 'medium') {
<span class="bg-light-warning text-warning rounded f-w-600 p-6 p-y-4 f-s-12">
{{ element.status | titlecase }}
</span>
}
@if(element.status == 'high') {
<span class="bg-light-primary text-primary rounded f-w-600 p-6 p-y-4 f-s-12">
{{ element.status | titlecase }}
</span>
}
@if(element.status == 'critical') {
<span class="bg-light-error text-error rounded f-w-600 p-6 p-y-4 f-s-12">
{{ element.status | titlecase }}
</span>
}
</td>
</ng-container>
<!-- Symbol Column -->
<ng-container matColumnDef="sales">
<th mat-header-cell *matHeaderCellDef class="f-w-600 f-s-14">
Sales
</th>
<td mat-cell *matCellDef="let element">
${{ element.sales }}k
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
</div>
</mat-card-content>
</mat-card>

View File

@@ -1,80 +1,80 @@
import { Component } from '@angular/core';
import { NgApexchartsModule } from 'ng-apexcharts';
import { TablerIconsModule } from 'angular-tabler-icons';
import { CommonModule } from '@angular/common';
import { MaterialModule } from 'src/app/material.module';
export interface performanceData {
id: number;
imagePath: string;
pname: string;
category: string;
progress: number;
sales: number;
status: string;
}
const ELEMENT_DATA: performanceData[] = [
{
id: 1,
imagePath: 'assets/images/products/s6.jpg',
pname: 'Gaming Console',
category: 'Electronics',
progress: 78.5,
sales: 3.9,
status: 'low',
},
{
id: 2,
imagePath: 'assets/images/products/s9.jpg',
pname: 'Leather Purse',
category: 'Fashion',
progress: 58.6,
sales: 3.5,
status: 'medium',
},
{
id: 3,
imagePath: 'assets/images/products/s7.jpg',
pname: 'Red Velvate Dress',
category: 'Womens Fashion',
progress: 25,
sales: 3.8,
status: 'high',
},
{
id: 4,
imagePath: 'assets/images/products/s4.jpg',
pname: 'Headphone Boat',
category: 'Electronics',
progress: 96.3,
sales: 3.54,
status: 'critical',
},
];
interface month {
value: string;
viewValue: string;
}
@Component({
selector: 'app-product-performance',
imports: [
NgApexchartsModule,
MaterialModule,
TablerIconsModule,
CommonModule,
],
templateUrl: './product-performance.component.html',
})
export class AppProductPerformanceComponent {
displayedColumns: string[] = ['product', 'progress', 'status', 'sales'];
dataSource = ELEMENT_DATA;
months: month[] = [
{ value: 'mar', viewValue: 'March 2025' },
{ value: 'apr', viewValue: 'April 2025' },
{ value: 'june', viewValue: 'June 2025' },
];
}
import { Component } from '@angular/core';
import { NgApexchartsModule } from 'ng-apexcharts';
import { TablerIconsModule } from 'angular-tabler-icons';
import { CommonModule } from '@angular/common';
import { MaterialModule } from 'src/app/material.module';
export interface performanceData {
id: number;
imagePath: string;
pname: string;
category: string;
progress: number;
sales: number;
status: string;
}
const ELEMENT_DATA: performanceData[] = [
{
id: 1,
imagePath: 'assets/images/products/s6.jpg',
pname: 'Gaming Console',
category: 'Electronics',
progress: 78.5,
sales: 3.9,
status: 'low',
},
{
id: 2,
imagePath: 'assets/images/products/s9.jpg',
pname: 'Leather Purse',
category: 'Fashion',
progress: 58.6,
sales: 3.5,
status: 'medium',
},
{
id: 3,
imagePath: 'assets/images/products/s7.jpg',
pname: 'Red Velvate Dress',
category: 'Womens Fashion',
progress: 25,
sales: 3.8,
status: 'high',
},
{
id: 4,
imagePath: 'assets/images/products/s4.jpg',
pname: 'Headphone Boat',
category: 'Electronics',
progress: 96.3,
sales: 3.54,
status: 'critical',
},
];
interface month {
value: string;
viewValue: string;
}
@Component({
selector: 'app-product-performance',
imports: [
NgApexchartsModule,
MaterialModule,
TablerIconsModule,
CommonModule,
],
templateUrl: './product-performance.component.html',
})
export class AppProductPerformanceComponent {
displayedColumns: string[] = ['product', 'progress', 'status', 'sales'];
dataSource = ELEMENT_DATA;
months: month[] = [
{ value: 'mar', viewValue: 'March 2025' },
{ value: 'apr', viewValue: 'April 2025' },
{ value: 'june', viewValue: 'June 2025' },
];
}

View File

@@ -1,33 +1,33 @@
<mat-card class="cardWithShadow">
<mat-card-content>
<mat-card-title>Recent Transactions</mat-card-title>
<div class="timeline m-t-24">
@for(stat of stats; track stat.title) {
<div class="timeline-item d-flex overflow-hidden">
<div class="time text-right f-s-14">{{ stat.time }}</div>
<div class="point d-flex align-items-center">
<span class="timeline-badge border-{{ stat.color }} m-y-8"></span>
<span class="timline-border d-block"></span>
</div>
<div class="desc">
@if(stat.subtext) {
<span class="f-s-14 lh-20">{{ stat.subtext }}</span>
}
@if(stat.title) {
<span class="f-s-14 lh-20 f-w-600 d-block">{{
stat.title
}}</span>
}
@if(stat.link) {
<a href="#" class="text-primary text-decoration-none f-s-14">#ML-3467</a>
}
</div>
</div>
}
</div>
</mat-card-content>
<mat-card class="cardWithShadow">
<mat-card-content>
<mat-card-title>Recent Transactions</mat-card-title>
<div class="timeline m-t-24">
@for(stat of stats; track stat.title) {
<div class="timeline-item d-flex overflow-hidden">
<div class="time text-right f-s-14">{{ stat.time }}</div>
<div class="point d-flex align-items-center">
<span class="timeline-badge border-{{ stat.color }} m-y-8"></span>
<span class="timline-border d-block"></span>
</div>
<div class="desc">
@if(stat.subtext) {
<span class="f-s-14 lh-20">{{ stat.subtext }}</span>
}
@if(stat.title) {
<span class="f-s-14 lh-20 f-w-600 d-block">{{
stat.title
}}</span>
}
@if(stat.link) {
<a href="#" class="text-primary text-decoration-none f-s-14">#ML-3467</a>
}
</div>
</div>
}
</div>
</mat-card-content>
</mat-card>

View File

@@ -1,62 +1,62 @@
import { Component } from '@angular/core';
import { NgApexchartsModule } from 'ng-apexcharts';
import { MaterialModule } from 'src/app/material.module';
interface stats {
id: number;
time: string;
color: string;
title?: string;
subtext?: string;
link?: string;
}
@Component({
selector: 'app-recent-transactions',
imports: [NgApexchartsModule, MaterialModule],
templateUrl: './recent-transactions.component.html',
})
export class AppRecentTransactionsComponent {
stats: stats[] = [
{
id: 1,
time: '09.30 am',
color: 'primary',
subtext: 'Payment received from John Doe of $385.90',
},
{
id: 2,
time: '10.30 am',
color: 'accent',
title: 'New sale recorded',
link: '#ML-3467',
},
{
id: 3,
time: '12.30 pm',
color: 'success',
subtext: 'Payment was made of $64.95 to Michael',
},
{
id: 4,
time: '12.30 pm',
color: 'warning',
title: 'New sale recorded',
link: '#ML-3467',
},
{
id: 5,
time: '12.30 pm',
color: 'error',
title: 'New arrival recorded',
link: '#ML-3467',
},
{
id: 6,
time: '12.30 pm',
color: 'success',
subtext: 'Payment Done',
},
];
}
import { Component } from '@angular/core';
import { NgApexchartsModule } from 'ng-apexcharts';
import { MaterialModule } from 'src/app/material.module';
interface stats {
id: number;
time: string;
color: string;
title?: string;
subtext?: string;
link?: string;
}
@Component({
selector: 'app-recent-transactions',
imports: [NgApexchartsModule, MaterialModule],
templateUrl: './recent-transactions.component.html',
})
export class AppRecentTransactionsComponent {
stats: stats[] = [
{
id: 1,
time: '09.30 am',
color: 'primary',
subtext: 'Payment received from John Doe of $385.90',
},
{
id: 2,
time: '10.30 am',
color: 'accent',
title: 'New sale recorded',
link: '#ML-3467',
},
{
id: 3,
time: '12.30 pm',
color: 'success',
subtext: 'Payment was made of $64.95 to Michael',
},
{
id: 4,
time: '12.30 pm',
color: 'warning',
title: 'New sale recorded',
link: '#ML-3467',
},
{
id: 5,
time: '12.30 pm',
color: 'error',
title: 'New arrival recorded',
link: '#ML-3467',
},
{
id: 6,
time: '12.30 pm',
color: 'success',
subtext: 'Payment Done',
},
];
}

View File

@@ -1,22 +1,22 @@
<mat-card class="cardWithShadow">
<mat-card-content>
<div class="d-flex w-100">
<mat-card-title>Sales Overview</mat-card-title>
<div class="m-l-auto">
<mat-form-field class="theme-select" appearance="outline">
<mat-select value="mar">
@for(month of months; track month.viewValue ) {
<mat-option [value]="month.value">
{{ month.viewValue }}
</mat-option>
}
</mat-select>
</mat-form-field>
</div>
</div>
<apx-chart [series]="salesOverviewChart.series" [dataLabels]="salesOverviewChart.dataLabels"
[chart]="salesOverviewChart.chart" [legend]="salesOverviewChart.legend" [xaxis]="salesOverviewChart.xaxis"
[yaxis]="salesOverviewChart.yaxis" [grid]="salesOverviewChart.grid" [stroke]="salesOverviewChart.stroke"
[tooltip]="salesOverviewChart.tooltip" [plotOptions]="salesOverviewChart.plotOptions"></apx-chart>
</mat-card-content>
<mat-card class="cardWithShadow">
<mat-card-content>
<div class="d-flex w-100">
<mat-card-title>Sales Overview</mat-card-title>
<div class="m-l-auto">
<mat-form-field class="theme-select" appearance="outline">
<mat-select value="mar">
@for(month of months; track month.viewValue ) {
<mat-option [value]="month.value">
{{ month.viewValue }}
</mat-option>
}
</mat-select>
</mat-form-field>
</div>
</div>
<apx-chart [series]="salesOverviewChart.series" [dataLabels]="salesOverviewChart.dataLabels"
[chart]="salesOverviewChart.chart" [legend]="salesOverviewChart.legend" [xaxis]="salesOverviewChart.xaxis"
[yaxis]="salesOverviewChart.yaxis" [grid]="salesOverviewChart.grid" [stroke]="salesOverviewChart.stroke"
[tooltip]="salesOverviewChart.tooltip" [plotOptions]="salesOverviewChart.plotOptions"></apx-chart>
</mat-card-content>
</mat-card>

View File

@@ -1,153 +1,153 @@
import { Component, ViewChild } from '@angular/core';
import { TablerIconsModule } from 'angular-tabler-icons';
import { MaterialModule } from 'src/app/material.module';
import {
ApexChart,
ChartComponent,
ApexDataLabels,
ApexLegend,
ApexStroke,
ApexTooltip,
ApexAxisChartSeries,
ApexXAxis,
ApexYAxis,
ApexGrid,
ApexPlotOptions,
ApexFill,
ApexMarkers,
ApexResponsive,
NgApexchartsModule,
} from 'ng-apexcharts';
import { MatButtonModule } from '@angular/material/button';
interface month {
value: string;
viewValue: string;
}
export interface salesOverviewChart {
series: ApexAxisChartSeries;
chart: ApexChart;
dataLabels: ApexDataLabels;
plotOptions: ApexPlotOptions;
yaxis: ApexYAxis;
xaxis: ApexXAxis;
fill: ApexFill;
tooltip: ApexTooltip;
stroke: ApexStroke;
legend: ApexLegend;
grid: ApexGrid;
marker: ApexMarkers;
}
@Component({
selector: 'app-sales-overview',
imports: [MaterialModule, TablerIconsModule, NgApexchartsModule, MatButtonModule],
templateUrl: './sales-overview.component.html',
})
export class AppSalesOverviewComponent {
@ViewChild('chart') chart: ChartComponent = Object.create(null);
public salesOverviewChart!: Partial<salesOverviewChart> | any;
months: month[] = [
{ value: 'mar', viewValue: 'Sep 2025' },
{ value: 'apr', viewValue: 'Oct 2025' },
{ value: 'june', viewValue: 'Nov 2025' },
];
constructor() {
// sales overview chart
this.salesOverviewChart = {
series: [
{
name: 'Eanings this month',
data: [355, 390, 300, 350, 390, 180, 355, 390],
color: '#5D87FF',
},
{
name: 'Expense this month',
data: [280, 250, 325, 215, 250, 310, 280, 250],
color: '#49BEFF',
},
],
grid: {
borderColor: 'rgba(0,0,0,0.1)',
strokeDashArray: 3,
xaxis: {
lines: {
show: false,
},
},
},
plotOptions: {
bar: { horizontal: false, columnWidth: '35%', borderRadius: [4] },
},
chart: {
type: 'bar',
height: 390,
offsetX: -15,
toolbar: { show: false },
foreColor: '#adb0bb',
fontFamily: 'inherit',
sparkline: { enabled: false },
},
dataLabels: { enabled: false },
markers: { size: 0 },
legend: { show: false },
xaxis: {
type: 'category',
categories: [
'16/08',
'17/08',
'18/08',
'19/08',
'20/08',
'21/08',
'22/08',
'23/08',
],
labels: {
style: { cssClass: 'grey--text lighten-2--text fill-color' },
},
},
yaxis: {
show: true,
min: 0,
max: 400,
tickAmount: 4,
labels: {
style: {
cssClass: 'grey--text lighten-2--text fill-color',
},
},
},
stroke: {
show: true,
width: 3,
lineCap: 'butt',
colors: ['transparent'],
},
tooltip: { theme: 'light' },
responsive: [
{
breakpoint: 600,
options: {
plotOptions: {
bar: {
borderRadius: 3,
},
},
},
},
],
};
}
}
import { Component, ViewChild } from '@angular/core';
import { TablerIconsModule } from 'angular-tabler-icons';
import { MaterialModule } from 'src/app/material.module';
import {
ApexChart,
ChartComponent,
ApexDataLabels,
ApexLegend,
ApexStroke,
ApexTooltip,
ApexAxisChartSeries,
ApexXAxis,
ApexYAxis,
ApexGrid,
ApexPlotOptions,
ApexFill,
ApexMarkers,
ApexResponsive,
NgApexchartsModule,
} from 'ng-apexcharts';
import { MatButtonModule } from '@angular/material/button';
interface month {
value: string;
viewValue: string;
}
export interface salesOverviewChart {
series: ApexAxisChartSeries;
chart: ApexChart;
dataLabels: ApexDataLabels;
plotOptions: ApexPlotOptions;
yaxis: ApexYAxis;
xaxis: ApexXAxis;
fill: ApexFill;
tooltip: ApexTooltip;
stroke: ApexStroke;
legend: ApexLegend;
grid: ApexGrid;
marker: ApexMarkers;
}
@Component({
selector: 'app-sales-overview',
imports: [MaterialModule, TablerIconsModule, NgApexchartsModule, MatButtonModule],
templateUrl: './sales-overview.component.html',
})
export class AppSalesOverviewComponent {
@ViewChild('chart') chart: ChartComponent = Object.create(null);
public salesOverviewChart!: Partial<salesOverviewChart> | any;
months: month[] = [
{ value: 'mar', viewValue: 'Sep 2025' },
{ value: 'apr', viewValue: 'Oct 2025' },
{ value: 'june', viewValue: 'Nov 2025' },
];
constructor() {
// sales overview chart
this.salesOverviewChart = {
series: [
{
name: 'Eanings this month',
data: [355, 390, 300, 350, 390, 180, 355, 390],
color: '#5D87FF',
},
{
name: 'Expense this month',
data: [280, 250, 325, 215, 250, 310, 280, 250],
color: '#49BEFF',
},
],
grid: {
borderColor: 'rgba(0,0,0,0.1)',
strokeDashArray: 3,
xaxis: {
lines: {
show: false,
},
},
},
plotOptions: {
bar: { horizontal: false, columnWidth: '35%', borderRadius: [4] },
},
chart: {
type: 'bar',
height: 390,
offsetX: -15,
toolbar: { show: false },
foreColor: '#adb0bb',
fontFamily: 'inherit',
sparkline: { enabled: false },
},
dataLabels: { enabled: false },
markers: { size: 0 },
legend: { show: false },
xaxis: {
type: 'category',
categories: [
'16/08',
'17/08',
'18/08',
'19/08',
'20/08',
'21/08',
'22/08',
'23/08',
],
labels: {
style: { cssClass: 'grey--text lighten-2--text fill-color' },
},
},
yaxis: {
show: true,
min: 0,
max: 400,
tickAmount: 4,
labels: {
style: {
cssClass: 'grey--text lighten-2--text fill-color',
},
},
},
stroke: {
show: true,
width: 3,
lineCap: 'butt',
colors: ['transparent'],
},
tooltip: { theme: 'light' },
responsive: [
{
breakpoint: 600,
options: {
plotOptions: {
bar: {
borderRadius: 3,
},
},
},
},
],
};
}
}

View File

@@ -1,34 +1,34 @@
<mat-card class="cardWithShadow">
<mat-card-content>
<mat-card-title>Yearly Breakup</mat-card-title>
<div class="row m-t-24">
<div class="col-7">
<h4 class="f-s-24">$36,358</h4>
<div class="d-flex align-items-center m-t-16">
<button mat-icon-button class="bg-light-success text-success shadow-none icon-27 p-0 d-flex align-items-center justify-content-center">
<i-tabler name="arrow-up-right" class="icon-20 d-flex align-items-center"></i-tabler>
</button>
<div class="f-w-600 m-l-12 f-s-14">+9%</div>
<div class="m-l-4 f-s-14">last year</div>
</div>
<div class="d-flex align-items-center m-t-32">
<div class="d-flex align-items-center">
<i-tabler name="circle-filled" class="text-primary icon-12 d-flex"></i-tabler>
<div class="m-l-12 f-s-14">2025</div>
</div>
<div class="d-flex align-items-center m-l-16">
<i-tabler name="circle-filled" class="text-light-primary icon-12 d-flex"></i-tabler>
<div class="m-l-12 f-s-14">2024</div>
</div>
</div>
</div>
<div class="col-5 d-flex align-items-start">
<apx-chart [series]="yearlyChart.series" [dataLabels]="yearlyChart.dataLabels"
[chart]="yearlyChart.chart" [legend]="yearlyChart.legend" [colors]="yearlyChart.colors"
[stroke]="yearlyChart.stroke" [tooltip]="yearlyChart.tooltip"
[plotOptions]="yearlyChart.plotOptions" [responsive]="yearlyChart.responsive"></apx-chart>
</div>
</div>
</mat-card-content>
<mat-card class="cardWithShadow">
<mat-card-content>
<mat-card-title>Yearly Breakup</mat-card-title>
<div class="row m-t-24">
<div class="col-7">
<h4 class="f-s-24">$36,358</h4>
<div class="d-flex align-items-center m-t-16">
<button mat-icon-button class="bg-light-success text-success shadow-none icon-27 p-0 d-flex align-items-center justify-content-center">
<i-tabler name="arrow-up-right" class="icon-20 d-flex align-items-center"></i-tabler>
</button>
<div class="f-w-600 m-l-12 f-s-14">+9%</div>
<div class="m-l-4 f-s-14">last year</div>
</div>
<div class="d-flex align-items-center m-t-32">
<div class="d-flex align-items-center">
<i-tabler name="circle-filled" class="text-primary icon-12 d-flex"></i-tabler>
<div class="m-l-12 f-s-14">2025</div>
</div>
<div class="d-flex align-items-center m-l-16">
<i-tabler name="circle-filled" class="text-light-primary icon-12 d-flex"></i-tabler>
<div class="m-l-12 f-s-14">2024</div>
</div>
</div>
</div>
<div class="col-5 d-flex align-items-start">
<apx-chart [series]="yearlyChart.series" [dataLabels]="yearlyChart.dataLabels"
[chart]="yearlyChart.chart" [legend]="yearlyChart.legend" [colors]="yearlyChart.colors"
[stroke]="yearlyChart.stroke" [tooltip]="yearlyChart.tooltip"
[plotOptions]="yearlyChart.plotOptions" [responsive]="yearlyChart.responsive"></apx-chart>
</div>
</div>
</mat-card-content>
</mat-card>

View File

@@ -1,101 +1,101 @@
import { Component, ViewEncapsulation, ViewChild } from '@angular/core';
import { TablerIconsModule } from 'angular-tabler-icons';
import {
ApexChart,
ChartComponent,
ApexDataLabels,
ApexLegend,
ApexStroke,
ApexTooltip,
ApexAxisChartSeries,
ApexXAxis,
ApexYAxis,
ApexGrid,
ApexPlotOptions,
ApexFill,
ApexMarkers,
ApexResponsive,
NgApexchartsModule,
} from 'ng-apexcharts';
import { MaterialModule } from 'src/app/material.module';
export interface yearlyChart {
series: ApexAxisChartSeries;
chart: ApexChart;
dataLabels: ApexDataLabels;
plotOptions: ApexPlotOptions;
tooltip: ApexTooltip;
stroke: ApexStroke;
legend: ApexLegend;
responsive: ApexResponsive;
}
@Component({
selector: 'app-yearly-breakup',
templateUrl: './yearly-breakup.component.html',
imports: [MaterialModule, NgApexchartsModule, TablerIconsModule],
encapsulation: ViewEncapsulation.None,
})
export class AppYearlyBreakupComponent {
@ViewChild('chart') chart: ChartComponent = Object.create(null);
public yearlyChart!: Partial<yearlyChart> | any;
constructor() {
this.yearlyChart = {
color: "#adb5bd",
series: [38, 40, 25],
labels: ["2025", "2024", "2023"],
chart: {
width: 125,
type: "donut",
fontFamily: "inherit",
foreColor: "#adb0bb",
},
plotOptions: {
pie: {
startAngle: 0,
endAngle: 360,
donut: {
size: "75%",
},
},
},
stroke: {
show: false,
},
dataLabels: {
enabled: false,
},
legend: {
show: false,
},
colors: ['#5D87FF', '#ECF2FF', '#F9F9FD'],
responsive: [
{
breakpoint: 991,
options: {
chart: {
width: 120,
},
},
},
],
tooltip: {
theme: "dark",
fillSeriesColor: false,
},
};
}
}
import { Component, ViewEncapsulation, ViewChild } from '@angular/core';
import { TablerIconsModule } from 'angular-tabler-icons';
import {
ApexChart,
ChartComponent,
ApexDataLabels,
ApexLegend,
ApexStroke,
ApexTooltip,
ApexAxisChartSeries,
ApexXAxis,
ApexYAxis,
ApexGrid,
ApexPlotOptions,
ApexFill,
ApexMarkers,
ApexResponsive,
NgApexchartsModule,
} from 'ng-apexcharts';
import { MaterialModule } from 'src/app/material.module';
export interface yearlyChart {
series: ApexAxisChartSeries;
chart: ApexChart;
dataLabels: ApexDataLabels;
plotOptions: ApexPlotOptions;
tooltip: ApexTooltip;
stroke: ApexStroke;
legend: ApexLegend;
responsive: ApexResponsive;
}
@Component({
selector: 'app-yearly-breakup',
templateUrl: './yearly-breakup.component.html',
imports: [MaterialModule, NgApexchartsModule, TablerIconsModule],
encapsulation: ViewEncapsulation.None,
})
export class AppYearlyBreakupComponent {
@ViewChild('chart') chart: ChartComponent = Object.create(null);
public yearlyChart!: Partial<yearlyChart> | any;
constructor() {
this.yearlyChart = {
color: "#adb5bd",
series: [38, 40, 25],
labels: ["2025", "2024", "2023"],
chart: {
width: 125,
type: "donut",
fontFamily: "inherit",
foreColor: "#adb0bb",
},
plotOptions: {
pie: {
startAngle: 0,
endAngle: 360,
donut: {
size: "75%",
},
},
},
stroke: {
show: false,
},
dataLabels: {
enabled: false,
},
legend: {
show: false,
},
colors: ['#5D87FF', '#ECF2FF', '#F9F9FD'],
responsive: [
{
breakpoint: 991,
options: {
chart: {
width: 120,
},
},
},
],
tooltip: {
theme: "dark",
fillSeriesColor: false,
},
};
}
}

View File

@@ -1,11 +1,11 @@
export interface AppSettings {
sidenavOpened: boolean;
sidenavCollapsed: boolean;
}
export const defaults: AppSettings = {
sidenavOpened: true,
sidenavCollapsed: true,
};
export interface AppSettings {
sidenavOpened: boolean;
sidenavCollapsed: boolean;
}
export const defaults: AppSettings = {
sidenavOpened: true,
sidenavCollapsed: true,
};

View File

@@ -1,6 +1,6 @@
<!-- ============================================================== -->
<!-- Only router without any element -->
<!-- ============================================================== -->
<mat-sidenav-container dir="ltr" class="light-theme">
<router-outlet></router-outlet>
<!-- ============================================================== -->
<!-- Only router without any element -->
<!-- ============================================================== -->
<mat-sidenav-container dir="ltr" class="light-theme">
<router-outlet></router-outlet>
</mat-sidenav-container>

View File

@@ -1,23 +1,23 @@
import { Component } from '@angular/core';
import { CoreService } from 'src/app/services/core.service';
import { CommonModule } from '@angular/common';
import { RouterOutlet } from '@angular/router';
import { MaterialModule } from 'src/app/material.module';
@Component({
selector: 'app-blank',
templateUrl: './blank.component.html',
styleUrls: [],
imports: [RouterOutlet, MaterialModule, CommonModule],
})
export class BlankComponent {
private htmlElement!: HTMLHtmlElement;
options = this.settings.getOptions();
constructor(private settings: CoreService) {
this.htmlElement = document.querySelector('html')!;
}
}
import { Component } from '@angular/core';
import { CoreService } from 'src/app/services/core.service';
import { CommonModule } from '@angular/common';
import { RouterOutlet } from '@angular/router';
import { MaterialModule } from 'src/app/material.module';
@Component({
selector: 'app-blank',
templateUrl: './blank.component.html',
styleUrls: [],
imports: [RouterOutlet, MaterialModule, CommonModule],
})
export class BlankComponent {
private htmlElement!: HTMLHtmlElement;
options = this.settings.getOptions();
constructor(private settings: CoreService) {
this.htmlElement = document.querySelector('html')!;
}
}

View File

@@ -1,59 +1,59 @@
<app-topstrip></app-topstrip>
<mat-sidenav-container class="mainWrapper blue_theme light-theme" autosize autoFocus dir="ltr">
<!-- ============================================================== -->
<!-- Vertical Sidebar -->
<!-- ============================================================== -->
<mat-sidenav #leftsidenav [mode]="isOver ? 'over' : 'side'" [opened]="!isOver"
(openedChange)="onSidenavOpenedChange($event)" (closedStart)="onSidenavClosedStart()" class="sidebarNav">
<div class="flex-layout">
<app-sidebar (toggleMobileNav)="sidenav.toggle()" [showToggle]="isOver"></app-sidebar>
<ng-scrollbar class="position-relative" style="height: 100%">
<mat-nav-list class="sidebar-list">
@for(item of navItems; track item) {
<app-nav-item [item]="item" (notify)="sidenav.toggle()">
</app-nav-item>
}
</mat-nav-list>
</ng-scrollbar>
<div class="p-24">
<div class="bg-light-secondary d-flex align-items-center gap-4 rounded p-20 m-t-20">
<div>
<h5 class="f-s-16 f-w-600 m-b-8">
Check Pro <br> Version
</h5>
<a mat-flat-button
href="https://adminmart.com/product/modernize-angular-material-dashboard/?ref=56#product-demo-section" target="_blank"
class="d-flex justify-content-center bg-secondary">
Check
</a>
</div>
<img src="/assets/images/backgrounds/rocket.png" alt="imgae" class="side-img m-t--48 m-r--8" />
</div>
</div>
</div>
</mat-sidenav>
<!-- ============================================================== -->
<!-- Main Content -->
<!-- ============================================================== -->
<mat-sidenav-content class="contentWrapper" #content>
<!-- ============================================================== -->
<!-- VerticalHeader -->
<!-- ============================================================== -->
<app-header [showToggle]="!isOver" (toggleMobileNav)="sidenav.toggle()"></app-header>
<main class="pageWrapper maxWidth">
<!-- ============================================================== -->
<!-- Outlet -->
<!-- ============================================================== -->
<router-outlet></router-outlet>
</main>
<div class="p-16 p-b-30 text-center">
Design & Developed by <a class="text-decoration-none" href="https://adminmart.com/" target="_blank">AdminMart.com</a>
</div>
</mat-sidenav-content>
<app-topstrip></app-topstrip>
<mat-sidenav-container class="mainWrapper blue_theme light-theme" autosize autoFocus dir="ltr">
<!-- ============================================================== -->
<!-- Vertical Sidebar -->
<!-- ============================================================== -->
<mat-sidenav #leftsidenav [mode]="isOver ? 'over' : 'side'" [opened]="!isOver"
(openedChange)="onSidenavOpenedChange($event)" (closedStart)="onSidenavClosedStart()" class="sidebarNav">
<div class="flex-layout">
<app-sidebar (toggleMobileNav)="sidenav.toggle()" [showToggle]="isOver"></app-sidebar>
<ng-scrollbar class="position-relative" style="height: 100%">
<mat-nav-list class="sidebar-list">
@for(item of navItems; track item) {
<app-nav-item [item]="item" (notify)="sidenav.toggle()">
</app-nav-item>
}
</mat-nav-list>
</ng-scrollbar>
<div class="p-24">
<div class="bg-light-secondary d-flex align-items-center gap-4 rounded p-20 m-t-20">
<div>
<h5 class="f-s-16 f-w-600 m-b-8">
Check Pro <br> Version
</h5>
<a mat-flat-button
href="https://adminmart.com/product/modernize-angular-material-dashboard/?ref=56#product-demo-section" target="_blank"
class="d-flex justify-content-center bg-secondary">
Check
</a>
</div>
<img src="/assets/images/backgrounds/rocket.png" alt="imgae" class="side-img m-t--48 m-r--8" />
</div>
</div>
</div>
</mat-sidenav>
<!-- ============================================================== -->
<!-- Main Content -->
<!-- ============================================================== -->
<mat-sidenav-content class="contentWrapper" #content>
<!-- ============================================================== -->
<!-- VerticalHeader -->
<!-- ============================================================== -->
<app-header [showToggle]="!isOver" (toggleMobileNav)="sidenav.toggle()"></app-header>
<main class="pageWrapper maxWidth">
<!-- ============================================================== -->
<!-- Outlet -->
<!-- ============================================================== -->
<router-outlet></router-outlet>
</main>
<div class="p-16 p-b-30 text-center">
Design & Developed by <a class="text-decoration-none" href="https://adminmart.com/" target="_blank">AdminMart.com</a>
</div>
</mat-sidenav-content>
</mat-sidenav-container>

View File

@@ -1,117 +1,117 @@
import { BreakpointObserver, MediaMatcher } from '@angular/cdk/layout';
import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { Subscription } from 'rxjs';
import { MatSidenav, MatSidenavContent } from '@angular/material/sidenav';
import { CoreService } from 'src/app/services/core.service';
import { filter } from 'rxjs/operators';
import { NavigationEnd, Router } from '@angular/router';
import { NavService } from '../../services/nav.service';
import { RouterModule } from '@angular/router';
import { MaterialModule } from 'src/app/material.module';
import { CommonModule } from '@angular/common';
import { NgScrollbarModule } from 'ngx-scrollbar';
import { TablerIconsModule } from 'angular-tabler-icons';
import { HeaderComponent } from './header/header.component';
import { SidebarComponent } from './sidebar/sidebar.component';
import { AppNavItemComponent } from './sidebar/nav-item/nav-item.component';
import { navItems } from './sidebar/sidebar-data';
import { AppTopstripComponent } from './top-strip/topstrip.component';
const MOBILE_VIEW = 'screen and (max-width: 768px)';
const TABLET_VIEW = 'screen and (min-width: 769px) and (max-width: 1024px)';
@Component({
selector: 'app-full',
imports: [
RouterModule,
AppNavItemComponent,
MaterialModule,
CommonModule,
SidebarComponent,
NgScrollbarModule,
TablerIconsModule,
HeaderComponent,
AppTopstripComponent
],
templateUrl: './full.component.html',
styleUrls: [],
encapsulation: ViewEncapsulation.None
})
export class FullComponent implements OnInit {
navItems = navItems;
@ViewChild('leftsidenav')
public sidenav: MatSidenav;
resView = false;
@ViewChild('content', { static: true }) content!: MatSidenavContent;
//get options from service
options = this.settings.getOptions();
private layoutChangesSubscription = Subscription.EMPTY;
private isMobileScreen = false;
private isContentWidthFixed = true;
private isCollapsedWidthFixed = false;
private htmlElement!: HTMLHtmlElement;
get isOver(): boolean {
return this.isMobileScreen;
}
constructor(
private settings: CoreService,
private router: Router,
private breakpointObserver: BreakpointObserver,
) {
this.htmlElement = document.querySelector('html')!;
this.layoutChangesSubscription = this.breakpointObserver
.observe([MOBILE_VIEW, TABLET_VIEW])
.subscribe((state) => {
// SidenavOpened must be reset true when layout changes
this.options.sidenavOpened = true;
this.isMobileScreen = state.breakpoints[MOBILE_VIEW];
if (this.options.sidenavCollapsed == false) {
this.options.sidenavCollapsed = state.breakpoints[TABLET_VIEW];
}
});
// Initialize project theme with options
// This is for scroll to top
this.router.events
.pipe(filter((event) => event instanceof NavigationEnd))
.subscribe((e) => {
this.content.scrollTo({ top: 0 });
});
}
ngOnInit(): void { }
ngOnDestroy() {
this.layoutChangesSubscription.unsubscribe();
}
toggleCollapsed() {
this.isContentWidthFixed = false;
this.options.sidenavCollapsed = !this.options.sidenavCollapsed;
this.resetCollapsedState();
}
resetCollapsedState(timer = 400) {
setTimeout(() => this.settings.setOptions(this.options), timer);
}
onSidenavClosedStart() {
this.isContentWidthFixed = false;
}
onSidenavOpenedChange(isOpened: boolean) {
this.isCollapsedWidthFixed = !this.isOver;
this.options.sidenavOpened = isOpened;
//this.settings.setOptions(this.options);
}
}
import { BreakpointObserver, MediaMatcher } from '@angular/cdk/layout';
import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { Subscription } from 'rxjs';
import { MatSidenav, MatSidenavContent } from '@angular/material/sidenav';
import { CoreService } from 'src/app/services/core.service';
import { filter } from 'rxjs/operators';
import { NavigationEnd, Router } from '@angular/router';
import { NavService } from '../../services/nav.service';
import { RouterModule } from '@angular/router';
import { MaterialModule } from 'src/app/material.module';
import { CommonModule } from '@angular/common';
import { NgScrollbarModule } from 'ngx-scrollbar';
import { TablerIconsModule } from 'angular-tabler-icons';
import { HeaderComponent } from './header/header.component';
import { SidebarComponent } from './sidebar/sidebar.component';
import { AppNavItemComponent } from './sidebar/nav-item/nav-item.component';
import { navItems } from './sidebar/sidebar-data';
import { AppTopstripComponent } from './top-strip/topstrip.component';
const MOBILE_VIEW = 'screen and (max-width: 768px)';
const TABLET_VIEW = 'screen and (min-width: 769px) and (max-width: 1024px)';
@Component({
selector: 'app-full',
imports: [
RouterModule,
AppNavItemComponent,
MaterialModule,
CommonModule,
SidebarComponent,
NgScrollbarModule,
TablerIconsModule,
HeaderComponent,
AppTopstripComponent
],
templateUrl: './full.component.html',
styleUrls: [],
encapsulation: ViewEncapsulation.None
})
export class FullComponent implements OnInit {
navItems = navItems;
@ViewChild('leftsidenav')
public sidenav: MatSidenav;
resView = false;
@ViewChild('content', { static: true }) content!: MatSidenavContent;
//get options from service
options = this.settings.getOptions();
private layoutChangesSubscription = Subscription.EMPTY;
private isMobileScreen = false;
private isContentWidthFixed = true;
private isCollapsedWidthFixed = false;
private htmlElement!: HTMLHtmlElement;
get isOver(): boolean {
return this.isMobileScreen;
}
constructor(
private settings: CoreService,
private router: Router,
private breakpointObserver: BreakpointObserver,
) {
this.htmlElement = document.querySelector('html')!;
this.layoutChangesSubscription = this.breakpointObserver
.observe([MOBILE_VIEW, TABLET_VIEW])
.subscribe((state) => {
// SidenavOpened must be reset true when layout changes
this.options.sidenavOpened = true;
this.isMobileScreen = state.breakpoints[MOBILE_VIEW];
if (this.options.sidenavCollapsed == false) {
this.options.sidenavCollapsed = state.breakpoints[TABLET_VIEW];
}
});
// Initialize project theme with options
// This is for scroll to top
this.router.events
.pipe(filter((event) => event instanceof NavigationEnd))
.subscribe((e) => {
this.content.scrollTo({ top: 0 });
});
}
ngOnInit(): void { }
ngOnDestroy() {
this.layoutChangesSubscription.unsubscribe();
}
toggleCollapsed() {
this.isContentWidthFixed = false;
this.options.sidenavCollapsed = !this.options.sidenavCollapsed;
this.resetCollapsedState();
}
resetCollapsedState(timer = 400) {
setTimeout(() => this.settings.setOptions(this.options), timer);
}
onSidenavClosedStart() {
this.isContentWidthFixed = false;
}
onSidenavOpenedChange(isOpened: boolean) {
this.isCollapsedWidthFixed = !this.isOver;
this.options.sidenavOpened = isOpened;
//this.settings.setOptions(this.options);
}
}

View File

@@ -1,48 +1,48 @@
<mat-toolbar class="topbar gap-10">
<!-- Mobile Menu -->
<button mat-icon-button (click)="toggleMobileNav.emit()" class="d-flex d-lg-none justify-content-center">
<i-tabler name="menu-2" class="icon-20 d-flex"></i-tabler>
</button>
<button mat-icon-button [matMenuTriggerFor]="menu" aria-label="Notifications">
<i-tabler class="d-flex" name="bell" matBadge="1" matBadgeColor="primary"></i-tabler>
</button>
<mat-menu #menu="matMenu">
<button mat-menu-item>Item 1</button>
<button mat-menu-item>Item 2</button>
</mat-menu>
<span class="flex-1-auto"></span>
<!-- --------------------------------------------------------------- -->
<!-- profile Dropdown -->
<!-- --------------------------------------------------------------- -->
<a mat-flat-button href="https://adminmart.com/product/modernize-angular-admin-dashboard/?ref=56#product-demo-section"
target="_blank" class="d-flex justify-content-center bg-success">
Check Pro Template
</a>
<button mat-mini-fab [matMenuTriggerFor]="profilemenu" aria-label="Notifications">
<img src="/assets/images/profile/user-1.jpg" class="rounded-circle object-cover d-block" width="35" />
</button>
<mat-menu #profilemenu="matMenu" class="cardWithShadow topbar-dd">
<button mat-menu-item>
<mat-icon class="d-flex align-items-center"><i-tabler name="user" class="icon-18 d-flex"></i-tabler></mat-icon>My
Profile
</button>
<button mat-menu-item>
<mat-icon class="d-flex align-items-center"><i-tabler name="mail" class="icon-18 d-flex"></i-tabler></mat-icon>My
Account
</button>
<button mat-menu-item>
<mat-icon class="d-flex align-items-center"><i-tabler name="list-check"
class="icon-18 d-flex"></i-tabler></mat-icon>My Task
</button>
<div class="p-x-12 m-t-12">
<a [routerLink]="['/authentication/login']" mat-stroked-button class="w-100">Logout</a>
</div>
</mat-menu>
<mat-toolbar class="topbar gap-10">
<!-- Mobile Menu -->
<button mat-icon-button (click)="toggleMobileNav.emit()" class="d-flex d-lg-none justify-content-center">
<i-tabler name="menu-2" class="icon-20 d-flex"></i-tabler>
</button>
<button mat-icon-button [matMenuTriggerFor]="menu" aria-label="Notifications">
<i-tabler class="d-flex" name="bell" matBadge="1" matBadgeColor="primary"></i-tabler>
</button>
<mat-menu #menu="matMenu">
<button mat-menu-item>Item 1</button>
<button mat-menu-item>Item 2</button>
</mat-menu>
<span class="flex-1-auto"></span>
<!-- --------------------------------------------------------------- -->
<!-- profile Dropdown -->
<!-- --------------------------------------------------------------- -->
<a mat-flat-button href="https://adminmart.com/product/modernize-angular-admin-dashboard/?ref=56#product-demo-section"
target="_blank" class="d-flex justify-content-center bg-success">
Check Pro Template
</a>
<button mat-mini-fab [matMenuTriggerFor]="profilemenu" aria-label="Notifications">
<img src="/assets/images/profile/user-1.jpg" class="rounded-circle object-cover d-block" width="35" />
</button>
<mat-menu #profilemenu="matMenu" class="cardWithShadow topbar-dd">
<button mat-menu-item>
<mat-icon class="d-flex align-items-center"><i-tabler name="user" class="icon-18 d-flex"></i-tabler></mat-icon>My
Profile
</button>
<button mat-menu-item>
<mat-icon class="d-flex align-items-center"><i-tabler name="mail" class="icon-18 d-flex"></i-tabler></mat-icon>My
Account
</button>
<button mat-menu-item>
<mat-icon class="d-flex align-items-center"><i-tabler name="list-check"
class="icon-18 d-flex"></i-tabler></mat-icon>My Task
</button>
<div class="p-x-12 m-t-12">
<a [routerLink]="['/authentication/login']" mat-stroked-button class="w-100">Logout</a>
</div>
</mat-menu>
</mat-toolbar>

View File

@@ -1,30 +1,30 @@
import {
Component,
Output,
EventEmitter,
Input,
ViewEncapsulation,
} from '@angular/core';
import { TablerIconsModule } from 'angular-tabler-icons';
import { MaterialModule } from 'src/app/material.module';
import { RouterModule } from '@angular/router';
import { CommonModule } from '@angular/common';
import { NgScrollbarModule } from 'ngx-scrollbar';
@Component({
selector: 'app-header',
imports: [
RouterModule,
CommonModule,
NgScrollbarModule,
TablerIconsModule,
MaterialModule,
],
templateUrl: './header.component.html',
encapsulation: ViewEncapsulation.None,
})
export class HeaderComponent {
@Input() showToggle = true;
@Input() toggleChecked = false;
@Output() toggleMobileNav = new EventEmitter<void>();
import {
Component,
Output,
EventEmitter,
Input,
ViewEncapsulation,
} from '@angular/core';
import { TablerIconsModule } from 'angular-tabler-icons';
import { MaterialModule } from 'src/app/material.module';
import { RouterModule } from '@angular/router';
import { CommonModule } from '@angular/common';
import { NgScrollbarModule } from 'ngx-scrollbar';
@Component({
selector: 'app-header',
imports: [
RouterModule,
CommonModule,
NgScrollbarModule,
TablerIconsModule,
MaterialModule,
],
templateUrl: './header.component.html',
encapsulation: ViewEncapsulation.None,
})
export class HeaderComponent {
@Input() showToggle = true;
@Input() toggleChecked = false;
@Output() toggleMobileNav = new EventEmitter<void>();
}

View File

@@ -1,20 +1,20 @@
import { Component } from '@angular/core';
import { CoreService } from 'src/app/services/core.service';
@Component({
selector: 'app-branding',
imports: [],
template: `
<a href="/" class="logodark">
<img
src="./assets/images/logos/dark-logo.svg"
class="align-middle m-2"
alt="logo"
/>
</a>
`,
})
export class BrandingComponent {
options = this.settings.getOptions();
constructor(private settings: CoreService) {}
}
import { Component } from '@angular/core';
import { CoreService } from 'src/app/services/core.service';
@Component({
selector: 'app-branding',
imports: [],
template: `
<a href="/" class="logodark">
<img
src="./assets/images/logos/dark-logo.svg"
class="align-middle m-2"
alt="logo"
/>
</a>
`,
})
export class BrandingComponent {
options = this.settings.getOptions();
constructor(private settings: CoreService) {}
}

View File

@@ -1,48 +1,48 @@
@if(item.navCap){
<div mat-subheader class="nav-caption">
{{ item.navCap }}
</div>
} @if(!item.navCap && !item.external) {
<a mat-list-item (click)="onItemSelected(item)" [ngClass]="{
activeMenu: item.route ? router.isActive(item.route, true) : false,
expanded: expanded,
disabled: item.disabled
}" class="menu-list-item">
<i-tabler class="routeIcon" name="{{ item.iconName }}" matListItemIcon></i-tabler>
<span class="hide-menu">{{ item.displayName }}</span>
@if(item.children && item.children.length) {
<span class="arrow-icon" fxFlex>
<span fxFlex></span>
@if(item.chip) {
<span>
<span class="{{ item.chipClass }} p-x-8 p-y-4 item-chip f-w-500 rounded-pill ">{{ item.chipContent }}</span>
</span>
}
<mat-icon [@indicatorRotate]="expanded ? 'expanded' : 'collapsed'">
expand_more
</mat-icon>
</span>
}
</a>
}
<!-- external Link -->
@if(!item.navCap && item.external) {
<mat-list-item (click)="openExternalLink(item.route)" class="menu-list-item" target="_blank">
<i-tabler class="routeIcon" name="{{ item.iconName }}" matListItemIcon></i-tabler>
<span class="hide-menu">{{ item.displayName }} </span>
@if(item.chip) {
<span>
<span class="{{ item.chipClass }} p-x-8 p-y-4 item-chip f-w-500 rounded-pill ">{{ item.chipContent }}</span>
</span>
}
</mat-list-item>
}
<!-- children -->
@if(expanded) { @for(child of item.children; track child) {
<app-nav-item [item]="child" (click)="onSubItemSelected(child)" [depth]="depth + 1">
</app-nav-item>
@if(item.navCap){
<div mat-subheader class="nav-caption">
{{ item.navCap }}
</div>
} @if(!item.navCap && !item.external) {
<a mat-list-item (click)="onItemSelected(item)" [ngClass]="{
activeMenu: item.route ? router.isActive(item.route, true) : false,
expanded: expanded,
disabled: item.disabled
}" class="menu-list-item">
<i-tabler class="routeIcon" name="{{ item.iconName }}" matListItemIcon></i-tabler>
<span class="hide-menu">{{ item.displayName }}</span>
@if(item.children && item.children.length) {
<span class="arrow-icon" fxFlex>
<span fxFlex></span>
@if(item.chip) {
<span>
<span class="{{ item.chipClass }} p-x-8 p-y-4 item-chip f-w-500 rounded-pill ">{{ item.chipContent }}</span>
</span>
}
<mat-icon [@indicatorRotate]="expanded ? 'expanded' : 'collapsed'">
expand_more
</mat-icon>
</span>
}
</a>
}
<!-- external Link -->
@if(!item.navCap && item.external) {
<mat-list-item (click)="openExternalLink(item.route)" class="menu-list-item" target="_blank">
<i-tabler class="routeIcon" name="{{ item.iconName }}" matListItemIcon></i-tabler>
<span class="hide-menu">{{ item.displayName }} </span>
@if(item.chip) {
<span>
<span class="{{ item.chipClass }} p-x-8 p-y-4 item-chip f-w-500 rounded-pill ">{{ item.chipContent }}</span>
</span>
}
</mat-list-item>
}
<!-- children -->
@if(expanded) { @for(child of item.children; track child) {
<app-nav-item [item]="child" (click)="onSubItemSelected(child)" [depth]="depth + 1">
</app-nav-item>
} }

View File

@@ -1,77 +1,77 @@
import {
Component,
HostBinding,
Input,
OnChanges,
Output,
EventEmitter,
} from '@angular/core';
import { NavItem } from './nav-item';
import { Router } from '@angular/router';
import { NavService } from '../../../../services/nav.service';
import { TranslateModule } from '@ngx-translate/core';
import { TablerIconsModule } from 'angular-tabler-icons';
import { MaterialModule } from 'src/app/material.module';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-nav-item',
imports: [TranslateModule, TablerIconsModule, MaterialModule, CommonModule],
templateUrl: './nav-item.component.html',
styleUrls: [],
})
export class AppNavItemComponent implements OnChanges {
@Output() notify: EventEmitter<boolean> = new EventEmitter<boolean>();
@Input() item: NavItem | any;
expanded: any = false;
@HostBinding('attr.aria-expanded') ariaExpanded = this.expanded;
@Input() depth: any;
constructor(public navService: NavService, public router: Router) {}
ngOnChanges() {
const url = this.navService.currentUrl();
if (this.item.route && url) {
this.expanded = url.indexOf(`/${this.item.route}`) === 0;
this.ariaExpanded = this.expanded;
}
}
onItemSelected(item: NavItem) {
if (!item.children || !item.children.length) {
this.router.navigate([item.route]);
}
if (item.children && item.children.length) {
this.expanded = !this.expanded;
}
//scroll
window.scroll({
top: 0,
left: 0,
behavior: 'smooth',
});
if (!this.expanded) {
if (window.innerWidth < 1024) {
this.notify.emit();
}
}
}
openExternalLink(url: string): void {
if (url) {
window.open(url, '_blank');
}
}
onSubItemSelected(item: NavItem) {
if (!item.children || !item.children.length) {
if (this.expanded && window.innerWidth < 1024) {
this.notify.emit();
}
}
}
}
import {
Component,
HostBinding,
Input,
OnChanges,
Output,
EventEmitter,
} from '@angular/core';
import { NavItem } from './nav-item';
import { Router } from '@angular/router';
import { NavService } from '../../../../services/nav.service';
import { TranslateModule } from '@ngx-translate/core';
import { TablerIconsModule } from 'angular-tabler-icons';
import { MaterialModule } from 'src/app/material.module';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-nav-item',
imports: [TranslateModule, TablerIconsModule, MaterialModule, CommonModule],
templateUrl: './nav-item.component.html',
styleUrls: [],
})
export class AppNavItemComponent implements OnChanges {
@Output() notify: EventEmitter<boolean> = new EventEmitter<boolean>();
@Input() item: NavItem | any;
expanded: any = false;
@HostBinding('attr.aria-expanded') ariaExpanded = this.expanded;
@Input() depth: any;
constructor(public navService: NavService, public router: Router) {}
ngOnChanges() {
const url = this.navService.currentUrl();
if (this.item.route && url) {
this.expanded = url.indexOf(`/${this.item.route}`) === 0;
this.ariaExpanded = this.expanded;
}
}
onItemSelected(item: NavItem) {
if (!item.children || !item.children.length) {
this.router.navigate([item.route]);
}
if (item.children && item.children.length) {
this.expanded = !this.expanded;
}
//scroll
window.scroll({
top: 0,
left: 0,
behavior: 'smooth',
});
if (!this.expanded) {
if (window.innerWidth < 1024) {
this.notify.emit();
}
}
}
openExternalLink(url: string): void {
if (url) {
window.open(url, '_blank');
}
}
onSubItemSelected(item: NavItem) {
if (!item.children || !item.children.length) {
if (this.expanded && window.innerWidth < 1024) {
this.notify.emit();
}
}
}
}

View File

@@ -1,11 +1,11 @@
export interface NavItem {
displayName?: string;
iconName?: string;
navCap?: string;
route?: string;
children?: NavItem[];
chip?: boolean;
chipContent?: string;
chipClass?: string;
external?: boolean;
export interface NavItem {
displayName?: string;
iconName?: string;
navCap?: string;
route?: string;
children?: NavItem[];
chip?: boolean;
chipContent?: string;
chipClass?: string;
external?: boolean;
}

View File

@@ -1,10 +1,10 @@
<div class="d-flex align-items-center justify-content-between">
<div class="branding"><app-branding></app-branding></div>
@if(showToggle) {
<a href="javascript:void(0)" (click)="toggleMobileNav.emit()"
class="d-flex justify-content-center ng-star-inserted icon-40 align-items-center">
<i-tabler name="x" class="icon-20 d-flex"></i-tabler>
</a>
}
<div class="d-flex align-items-center justify-content-between">
<div class="branding"><app-branding></app-branding></div>
@if(showToggle) {
<a href="javascript:void(0)" (click)="toggleMobileNav.emit()"
class="d-flex justify-content-center ng-star-inserted icon-40 align-items-center">
<i-tabler name="x" class="icon-20 d-flex"></i-tabler>
</a>
}
</div>

View File

@@ -1,25 +1,25 @@
import {
Component,
EventEmitter,
Input,
OnInit,
Output,
ViewChild,
} from '@angular/core';
import { BrandingComponent } from './branding.component';
import { TablerIconsModule } from 'angular-tabler-icons';
import { MaterialModule } from 'src/app/material.module';
@Component({
selector: 'app-sidebar',
imports: [BrandingComponent, TablerIconsModule, MaterialModule],
templateUrl: './sidebar.component.html',
})
export class SidebarComponent implements OnInit {
constructor() {}
@Input() showToggle = true;
@Output() toggleMobileNav = new EventEmitter<void>();
@Output() toggleCollapsed = new EventEmitter<void>();
ngOnInit(): void {}
}
import {
Component,
EventEmitter,
Input,
OnInit,
Output,
ViewChild,
} from '@angular/core';
import { BrandingComponent } from './branding.component';
import { TablerIconsModule } from 'angular-tabler-icons';
import { MaterialModule } from 'src/app/material.module';
@Component({
selector: 'app-sidebar',
imports: [BrandingComponent, TablerIconsModule, MaterialModule],
templateUrl: './sidebar.component.html',
})
export class SidebarComponent implements OnInit {
constructor() {}
@Input() showToggle = true;
@Output() toggleMobileNav = new EventEmitter<void>();
@Output() toggleCollapsed = new EventEmitter<void>();
ngOnInit(): void {}
}

View File

@@ -1,121 +1,121 @@
<div class="app-topstrip bg-dark text-white p-y-16 p-x-24 w-100 d-flex align-items-center justify-content-between">
<div class="d-none d-sm-flex align-items-center justify-content-center gap-20">
<a class="d-flex align-items-center" href="https://adminmart.com/" target="_blank">
<img src="/assets/images/logos/logo-adminmart.svg" alt="" width="150">
</a>
<div class="linkbar d-none d-lg-flex align-items-center justify-content-center gap-16">
<!-- Templates -->
<a mat-button class="link-hover p-x-0 text-white d-flex" href="https://adminmart.com/templates/angular/"
target="_blank">
<div class="d-flex align-items-center gap-8 f-s-16 f-w-400">
<i class="iconify icon-20 d-flex" data-icon="solar:window-frame-linear"></i>
Templates
</div>
</a>
<!-- Support -->
<a mat-button class="link-hover p-x-0 text-white d-flex" href="https://adminmart.com/support/"
target="_blank">
<div class="d-flex align-items-center gap-8 f-s-16 f-w-400">
<i class="iconify icon-20 d-flex" data-icon="solar:question-circle-linear"></i>
Help
</div>
</a>
<!-- Hire Us -->
<a mat-button class="link-hover p-x-0 text-white d-flex" href="https://adminmart.com/hire-us/"
target="_blank">
<div class="d-flex align-items-center gap-8 f-s-16 f-w-400">
<i class="iconify icon-20 d-flex" data-icon="solar:case-round-linear"></i>
Hire Us
</div>
</a>
</div>
</div>
<div class="topstrip-right d-flex align-items-center justify-content-center gap-16">
<h6 class="text-linear-gradient f-s-14 text-uppercase text-lg-left text-center">Checkout Pro Version</h6>
<div class="topstrip-right-inner d-flex justify-content-center align-items-center gap-10">
<div class="d-flex justify-content-center align-items-center gap-10">
<!-- Live Preview -->
<button class="live-preview-drop d-flex align-items-center gap-4 text-white f-s-16 p-x-16" mat-button
[matMenuTriggerFor]="beforeMenu">
<div class="d-flex align-items-center gap-6">
Live Preview
<i class="iconify icon-20 d-flex" data-icon="solar:alt-arrow-down-linear"></i>
</div>
</button>
<mat-menu class="p-x-16 p-y-8 rounded-7" #beforeMenu="matMenu" xPosition="before">
<a mat-menu-item class="rounded-7"
href="https://adminmart.com/product/modernize-angular-material-dashboard/?ref=56#product-demo-section"
target="_blank">
<div class="d-flex align-items-center gap-12 f-s-16">
<img src="/assets/images/svgs/angular-cat-icon.svg" width="18" alt="angular">
Angular Version
</div>
</a>
<a mat-menu-item class="rounded-7"
href="https://adminmart.com/product/modernize-react-mui-dashboard-theme/?ref=56#product-demo-section"
target="_blank">
<div class="d-flex align-items-center gap-12 f-s-16">
<img src="/assets/images/svgs/react-cat-icon.svg" width="18" alt="react">
React Version
</div>
</a>
<a mat-menu-item class="rounded-7"
href="https://adminmart.com/product/modernize-vuetify-vue-admin-dashboard/?ref=56#product-demo-section"
target="_blank">
<div class="d-flex align-items-center gap-12 f-s-16">
<img src="/assets/images/svgs/vue-cat-icon.svg" width="18" alt="vueJs">
VueJs Version
</div>
</a>
<a mat-menu-item class="rounded-7"
href="https://adminmart.com/product/modernize-next-js-admin-dashboard/?ref=56#product-demo-section"
target="_blank">
<div class="d-flex align-items-center gap-12 f-s-16">
<img src="/assets/images/svgs/next-cat-icon.svg" width="18" alt="nextJs">
NextJs Version
</div>
</a>
<a mat-menu-item class="rounded-7"
href="https://adminmart.com/product/modernize-nuxt-js-admin-dashboard/?ref=56#product-demo-section"
target="_blank">
<div class="d-flex align-items-center gap-12 f-s-16">
<img src="/assets/images/svgs/nuxt-cat-icon.svg" width="18" alt="nuxtJs">
NuxtJs Version
</div>
</a>
<a mat-menu-item class="rounded-7"
href="https://adminmart.com/product/modernize-tailwind-nextjs-dashboard-template/?ref=56#product-demo-section"
target="_blank">
<div class="d-flex align-items-center gap-12 f-s-16">
<img src="/assets/images/svgs/tailwindcss.svg" width="20" alt="tailwind">
Tailwind Version
</div>
</a>
<a mat-menu-item class="rounded-7"
href="https://adminmart.com/product/modernize-bootstrap-5-admin-template/?ref=56#product-demo-section"
target="_blank">
<div class="d-flex align-items-center gap-12 f-s-16">
<img src="/assets/images/svgs/bt-cat-icon.svg" width="20" alt="bootstrap">
Bootstrap Version
</div>
</a>
</mat-menu>
<!-- Get Pro -->
<a mat-button class="get-pro-btn text-white p-x-16"
href="https://adminmart.com/product/modernize-angular-material-dashboard/?ref=56" target="_blank">
<div class="d-flex align-items-center gap-8 f-s-16 f-w-400">
<i class="iconify icon-18 d-flex" data-icon="solar:crown-linear"></i>
Get Pro
</div>
</a>
</div>
</div>
</div>
<div class="app-topstrip bg-dark text-white p-y-16 p-x-24 w-100 d-flex align-items-center justify-content-between">
<div class="d-none d-sm-flex align-items-center justify-content-center gap-20">
<a class="d-flex align-items-center" href="https://adminmart.com/" target="_blank">
<img src="/assets/images/logos/logo-adminmart.svg" alt="" width="150">
</a>
<div class="linkbar d-none d-lg-flex align-items-center justify-content-center gap-16">
<!-- Templates -->
<a mat-button class="link-hover p-x-0 text-white d-flex" href="https://adminmart.com/templates/angular/"
target="_blank">
<div class="d-flex align-items-center gap-8 f-s-16 f-w-400">
<i class="iconify icon-20 d-flex" data-icon="solar:window-frame-linear"></i>
Templates
</div>
</a>
<!-- Support -->
<a mat-button class="link-hover p-x-0 text-white d-flex" href="https://adminmart.com/support/"
target="_blank">
<div class="d-flex align-items-center gap-8 f-s-16 f-w-400">
<i class="iconify icon-20 d-flex" data-icon="solar:question-circle-linear"></i>
Help
</div>
</a>
<!-- Hire Us -->
<a mat-button class="link-hover p-x-0 text-white d-flex" href="https://adminmart.com/hire-us/"
target="_blank">
<div class="d-flex align-items-center gap-8 f-s-16 f-w-400">
<i class="iconify icon-20 d-flex" data-icon="solar:case-round-linear"></i>
Hire Us
</div>
</a>
</div>
</div>
<div class="topstrip-right d-flex align-items-center justify-content-center gap-16">
<h6 class="text-linear-gradient f-s-14 text-uppercase text-lg-left text-center">Checkout Pro Version</h6>
<div class="topstrip-right-inner d-flex justify-content-center align-items-center gap-10">
<div class="d-flex justify-content-center align-items-center gap-10">
<!-- Live Preview -->
<button class="live-preview-drop d-flex align-items-center gap-4 text-white f-s-16 p-x-16" mat-button
[matMenuTriggerFor]="beforeMenu">
<div class="d-flex align-items-center gap-6">
Live Preview
<i class="iconify icon-20 d-flex" data-icon="solar:alt-arrow-down-linear"></i>
</div>
</button>
<mat-menu class="p-x-16 p-y-8 rounded-7" #beforeMenu="matMenu" xPosition="before">
<a mat-menu-item class="rounded-7"
href="https://adminmart.com/product/modernize-angular-material-dashboard/?ref=56#product-demo-section"
target="_blank">
<div class="d-flex align-items-center gap-12 f-s-16">
<img src="/assets/images/svgs/angular-cat-icon.svg" width="18" alt="angular">
Angular Version
</div>
</a>
<a mat-menu-item class="rounded-7"
href="https://adminmart.com/product/modernize-react-mui-dashboard-theme/?ref=56#product-demo-section"
target="_blank">
<div class="d-flex align-items-center gap-12 f-s-16">
<img src="/assets/images/svgs/react-cat-icon.svg" width="18" alt="react">
React Version
</div>
</a>
<a mat-menu-item class="rounded-7"
href="https://adminmart.com/product/modernize-vuetify-vue-admin-dashboard/?ref=56#product-demo-section"
target="_blank">
<div class="d-flex align-items-center gap-12 f-s-16">
<img src="/assets/images/svgs/vue-cat-icon.svg" width="18" alt="vueJs">
VueJs Version
</div>
</a>
<a mat-menu-item class="rounded-7"
href="https://adminmart.com/product/modernize-next-js-admin-dashboard/?ref=56#product-demo-section"
target="_blank">
<div class="d-flex align-items-center gap-12 f-s-16">
<img src="/assets/images/svgs/next-cat-icon.svg" width="18" alt="nextJs">
NextJs Version
</div>
</a>
<a mat-menu-item class="rounded-7"
href="https://adminmart.com/product/modernize-nuxt-js-admin-dashboard/?ref=56#product-demo-section"
target="_blank">
<div class="d-flex align-items-center gap-12 f-s-16">
<img src="/assets/images/svgs/nuxt-cat-icon.svg" width="18" alt="nuxtJs">
NuxtJs Version
</div>
</a>
<a mat-menu-item class="rounded-7"
href="https://adminmart.com/product/modernize-tailwind-nextjs-dashboard-template/?ref=56#product-demo-section"
target="_blank">
<div class="d-flex align-items-center gap-12 f-s-16">
<img src="/assets/images/svgs/tailwindcss.svg" width="20" alt="tailwind">
Tailwind Version
</div>
</a>
<a mat-menu-item class="rounded-7"
href="https://adminmart.com/product/modernize-bootstrap-5-admin-template/?ref=56#product-demo-section"
target="_blank">
<div class="d-flex align-items-center gap-12 f-s-16">
<img src="/assets/images/svgs/bt-cat-icon.svg" width="20" alt="bootstrap">
Bootstrap Version
</div>
</a>
</mat-menu>
<!-- Get Pro -->
<a mat-button class="get-pro-btn text-white p-x-16"
href="https://adminmart.com/product/modernize-angular-material-dashboard/?ref=56" target="_blank">
<div class="d-flex align-items-center gap-8 f-s-16 f-w-400">
<i class="iconify icon-18 d-flex" data-icon="solar:crown-linear"></i>
Get Pro
</div>
</a>
</div>
</div>
</div>
</div>

View File

@@ -1,15 +1,15 @@
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatMenuModule } from '@angular/material/menu';
import { TablerIconsModule } from 'angular-tabler-icons';
@Component({
selector: 'app-topstrip',
imports: [TablerIconsModule, MatButtonModule, MatMenuModule],
templateUrl: './topstrip.component.html',
})
export class AppTopstripComponent {
constructor() { }
}
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatMenuModule } from '@angular/material/menu';
import { TablerIconsModule } from 'angular-tabler-icons';
@Component({
selector: 'app-topstrip',
imports: [TablerIconsModule, MatButtonModule, MatMenuModule],
templateUrl: './topstrip.component.html',
})
export class AppTopstripComponent {
constructor() { }
}

View File

@@ -1,85 +1,85 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
// Material Form Controls
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatRadioModule } from '@angular/material/radio';
import { MatSelectModule } from '@angular/material/select';
import { MatSliderModule } from '@angular/material/slider';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
// Material Navigation
import { MatMenuModule } from '@angular/material/menu';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatToolbarModule } from '@angular/material/toolbar';
// Material Layout
import { MatCardModule } from '@angular/material/card';
import { MatDividerModule } from '@angular/material/divider';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatGridListModule } from '@angular/material/grid-list';
import { MatListModule } from '@angular/material/list';
import { MatStepperModule } from '@angular/material/stepper';
import { MatTabsModule } from '@angular/material/tabs';
import { MatTreeModule } from '@angular/material/tree';
// Material Buttons & Indicators
import { MatButtonModule } from '@angular/material/button';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatBadgeModule } from '@angular/material/badge';
import { MatChipsModule } from '@angular/material/chips';
import { MatIconModule } from '@angular/material/icon';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatRippleModule } from '@angular/material/core';
// Material Popups & Modals
import { MatBottomSheetModule } from '@angular/material/bottom-sheet';
import { MatDialogModule } from '@angular/material/dialog';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatTooltipModule } from '@angular/material/tooltip';
// Material Data tables
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatSortModule } from '@angular/material/sort';
import { MatTableModule } from '@angular/material/table';
@NgModule({
declarations: [],
exports: [
MatAutocompleteModule,
MatCheckboxModule,
MatDatepickerModule,
MatFormFieldModule,
MatInputModule,
MatRadioModule,
MatSelectModule,
MatSliderModule,
MatSlideToggleModule,
MatMenuModule,
MatSidenavModule,
MatToolbarModule,
MatCardModule,
MatDividerModule,
MatExpansionModule,
MatGridListModule,
MatListModule,
MatStepperModule,
MatTabsModule,
MatTreeModule,
MatButtonModule,
MatButtonToggleModule,
MatBadgeModule,
MatChipsModule,
MatIconModule,
MatProgressSpinnerModule,
MatProgressBarModule,
MatRippleModule,
MatBottomSheetModule,
MatDialogModule,
MatSnackBarModule,
MatTooltipModule,
MatPaginatorModule,
MatSortModule,
MatTableModule,
],
})
export class MaterialModule {}
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
// Material Form Controls
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatRadioModule } from '@angular/material/radio';
import { MatSelectModule } from '@angular/material/select';
import { MatSliderModule } from '@angular/material/slider';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
// Material Navigation
import { MatMenuModule } from '@angular/material/menu';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatToolbarModule } from '@angular/material/toolbar';
// Material Layout
import { MatCardModule } from '@angular/material/card';
import { MatDividerModule } from '@angular/material/divider';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatGridListModule } from '@angular/material/grid-list';
import { MatListModule } from '@angular/material/list';
import { MatStepperModule } from '@angular/material/stepper';
import { MatTabsModule } from '@angular/material/tabs';
import { MatTreeModule } from '@angular/material/tree';
// Material Buttons & Indicators
import { MatButtonModule } from '@angular/material/button';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatBadgeModule } from '@angular/material/badge';
import { MatChipsModule } from '@angular/material/chips';
import { MatIconModule } from '@angular/material/icon';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatRippleModule } from '@angular/material/core';
// Material Popups & Modals
import { MatBottomSheetModule } from '@angular/material/bottom-sheet';
import { MatDialogModule } from '@angular/material/dialog';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatTooltipModule } from '@angular/material/tooltip';
// Material Data tables
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatSortModule } from '@angular/material/sort';
import { MatTableModule } from '@angular/material/table';
@NgModule({
declarations: [],
exports: [
MatAutocompleteModule,
MatCheckboxModule,
MatDatepickerModule,
MatFormFieldModule,
MatInputModule,
MatRadioModule,
MatSelectModule,
MatSliderModule,
MatSlideToggleModule,
MatMenuModule,
MatSidenavModule,
MatToolbarModule,
MatCardModule,
MatDividerModule,
MatExpansionModule,
MatGridListModule,
MatListModule,
MatStepperModule,
MatTabsModule,
MatTreeModule,
MatButtonModule,
MatButtonToggleModule,
MatBadgeModule,
MatChipsModule,
MatIconModule,
MatProgressSpinnerModule,
MatProgressBarModule,
MatRippleModule,
MatBottomSheetModule,
MatDialogModule,
MatSnackBarModule,
MatTooltipModule,
MatPaginatorModule,
MatSortModule,
MatTableModule,
],
})
export class MaterialModule {}

View File

@@ -1,20 +1,20 @@
import { Routes } from '@angular/router';
import { AppSideLoginComponent } from './side-login/side-login.component';
import { AppSideRegisterComponent } from './side-register/side-register.component';
export const AuthenticationRoutes: Routes = [
{
path: '',
children: [
{
path: 'login',
component: AppSideLoginComponent,
},
{
path: 'register',
component: AppSideRegisterComponent,
},
],
},
];
import { Routes } from '@angular/router';
import { AppSideLoginComponent } from './side-login/side-login.component';
import { AppSideRegisterComponent } from './side-register/side-register.component';
export const AuthenticationRoutes: Routes = [
{
path: '',
children: [
{
path: 'login',
component: AppSideLoginComponent,
},
{
path: 'register',
component: AppSideRegisterComponent,
},
],
},
];

View File

@@ -1,64 +1,64 @@
<div class="blank-layout-container justify-content-center align-items-center bg-light">
<div class="position-relative row w-100 h-100 bg-gredient justify-content-center">
<div class="col-lg-4 d-flex align-items-center">
<mat-card class="cardWithShadow boxed-auth">
<mat-card-content class="p-32">
<div class="text-center">
<a [routerLink]="['/dashboard']">
<img src="./assets/images/logos/dark-logo.svg" class="align-middle m-2" alt="logo" />
</a>
</div>
<div class="row m-t-24 custom-row">
<div class="col-12 col-sm-6">
<button mat-stroked-button class="w-100">
<div class="d-flex align-items-center">
<img src="/assets/images/svgs/google-icon.svg" alt="google" width="16" class="m-r-8" />
<span>
Sign in with Google
</span>
</div>
</button>
</div>
<div class="col-12 col-sm-6">
<button mat-stroked-button class="w-100 d-flex align-items-center">
<div class="d-flex align-items-center">
<img src="/assets/images/svgs/facebook-icon.svg" alt="facebook" width="40" class="m-r-4" />
Sign in with FB
</div>
</button>
</div>
</div>
<div class="or-border m-t-30">or sign in with</div>
<form class="m-t-30">
<mat-label class="f-s-14 f-w-600 m-b-12 d-block">Username</mat-label>
<mat-form-field appearance="outline" class="w-100" color="primary">
<input matInput />
</mat-form-field>
<!-- password -->
<mat-label class="f-s-14 f-w-600 m-b-12 d-block">Password</mat-label>
<mat-form-field appearance="outline" class="w-100" color="primary">
<input matInput type="password" />
</mat-form-field>
<div class="d-flex align-items-center m-b-12">
<mat-checkbox color="primary">Remember this Device</mat-checkbox>
<a [routerLink]="['/']" class="text-primary f-w-600 text-decoration-none m-l-auto f-s-14">Forgot Password
?</a>
</div>
<a [routerLink]="['/']" mat-flat-button color="primary" class="w-100">
Sign In
</a>
<!-- input -->
</form>
<span class="d-block f-w-500 text-center m-t-24">New to Modernize?
<a [routerLink]="['/authentication/register']" class="text-decoration-none text-primary f-w-500 f-s-14">
Create an account</a>
</span>
</mat-card-content>
</mat-card>
</div>
</div>
<div class="blank-layout-container justify-content-center align-items-center bg-light">
<div class="position-relative row w-100 h-100 bg-gredient justify-content-center">
<div class="col-lg-4 d-flex align-items-center">
<mat-card class="cardWithShadow boxed-auth">
<mat-card-content class="p-32">
<div class="text-center">
<a [routerLink]="['/dashboard']">
<img src="./assets/images/logos/dark-logo.svg" class="align-middle m-2" alt="logo" />
</a>
</div>
<div class="row m-t-24 custom-row">
<div class="col-12 col-sm-6">
<button mat-stroked-button class="w-100">
<div class="d-flex align-items-center">
<img src="/assets/images/svgs/google-icon.svg" alt="google" width="16" class="m-r-8" />
<span>
Sign in with Google
</span>
</div>
</button>
</div>
<div class="col-12 col-sm-6">
<button mat-stroked-button class="w-100 d-flex align-items-center">
<div class="d-flex align-items-center">
<img src="/assets/images/svgs/facebook-icon.svg" alt="facebook" width="40" class="m-r-4" />
Sign in with FB
</div>
</button>
</div>
</div>
<div class="or-border m-t-30">or sign in with</div>
<form class="m-t-30">
<mat-label class="f-s-14 f-w-600 m-b-12 d-block">Username</mat-label>
<mat-form-field appearance="outline" class="w-100" color="primary">
<input matInput />
</mat-form-field>
<!-- password -->
<mat-label class="f-s-14 f-w-600 m-b-12 d-block">Password</mat-label>
<mat-form-field appearance="outline" class="w-100" color="primary">
<input matInput type="password" />
</mat-form-field>
<div class="d-flex align-items-center m-b-12">
<mat-checkbox color="primary">Remember this Device</mat-checkbox>
<a [routerLink]="['/']" class="text-primary f-w-600 text-decoration-none m-l-auto f-s-14">Forgot Password
?</a>
</div>
<a [routerLink]="['/']" mat-flat-button color="primary" class="w-100">
Sign In
</a>
<!-- input -->
</form>
<span class="d-block f-w-500 text-center m-t-24">New to Modernize?
<a [routerLink]="['/authentication/register']" class="text-decoration-none text-primary f-w-500 f-s-14">
Create an account</a>
</span>
</mat-card-content>
</mat-card>
</div>
</div>
</div>

View File

@@ -1,31 +1,31 @@
import { Component } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { RouterModule } from '@angular/router';
import { MaterialModule } from 'src/app/material.module';
import { FormsModule } from '@angular/forms';
import { ReactiveFormsModule } from '@angular/forms';
@Component({
selector: 'app-side-login',
imports: [RouterModule, MaterialModule, FormsModule, ReactiveFormsModule],
templateUrl: './side-login.component.html',
})
export class AppSideLoginComponent {
constructor( private router: Router) {}
form = new FormGroup({
uname: new FormControl('', [Validators.required, Validators.minLength(6)]),
password: new FormControl('', [Validators.required]),
});
get f() {
return this.form.controls;
}
submit() {
// console.log(this.form.value);
this.router.navigate(['/']);
}
}
import { Component } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { RouterModule } from '@angular/router';
import { MaterialModule } from 'src/app/material.module';
import { FormsModule } from '@angular/forms';
import { ReactiveFormsModule } from '@angular/forms';
@Component({
selector: 'app-side-login',
imports: [RouterModule, MaterialModule, FormsModule, ReactiveFormsModule],
templateUrl: './side-login.component.html',
})
export class AppSideLoginComponent {
constructor( private router: Router) {}
form = new FormGroup({
uname: new FormControl('', [Validators.required, Validators.minLength(6)]),
password: new FormControl('', [Validators.required]),
});
get f() {
return this.form.controls;
}
submit() {
// console.log(this.form.value);
this.router.navigate(['/']);
}
}

View File

@@ -1,62 +1,62 @@
<div class="blank-layout-container justify-content-center align-items-center bg-light">
<div class="position-relative row w-100 h-100 bg-gredient justify-content-center">
<div class="col-lg-4 d-flex align-items-center">
<mat-card class="cardWithShadow boxed-auth">
<mat-card-content class="p-32">
<div class="text-center">
<a [routerLink]="['/dashboard']">
<img src="./assets/images/logos/dark-logo.svg" class="align-middle m-2" alt="logo" />
</a>
</div>
<div class="row m-t-24 custom-row">
<div class="col-12 col-sm-6">
<button mat-stroked-button class="w-100">
<div class="d-flex align-items-center">
<img src="/assets/images/svgs/google-icon.svg" alt="google" width="16" class="m-r-8" />
Sign in with Google
</div>
</button>
</div>
<div class="col-12 col-sm-6">
<button mat-stroked-button class="w-100 d-flex align-items-center">
<div class="d-flex align-items-center">
<img src="/assets/images/svgs/facebook-icon.svg" alt="facebook" width="40" class="m-r-4" />
Sign in with FB
</div>
</button>
</div>
</div>
<div class="or-border m-t-30">or sign up with</div>
<form class="m-t-30">
<mat-label class="f-s-14 f-w-600 m-b-12 d-block">Name</mat-label>
<mat-form-field appearance="outline" class="w-100" color="primary">
<input matInput />
</mat-form-field>
<mat-label class="f-s-14 f-w-600 m-b-12 d-block">Email Adddress</mat-label>
<mat-form-field appearance="outline" class="w-100" color="primary">
<input matInput type="email" />
</mat-form-field>
<!-- password -->
<mat-label class="f-s-14 f-w-600 m-b-12 d-block">Password</mat-label>
<mat-form-field appearance="outline" class="w-100" color="primary">
<input matInput type="password" />
</mat-form-field>
<a [routerLink]="['/']" mat-flat-button color="primary" class="w-100">
Sign Up
</a>
<!-- input -->
</form>
<span class="d-block f-w-500 text-center m-t-24">Already have an Account?
<a [routerLink]="['/authentication/login']" class="text-decoration-none text-primary f-w-500 f-s-14">
Sign In</a>
</span>
</mat-card-content>
</mat-card>
</div>
</div>
<div class="blank-layout-container justify-content-center align-items-center bg-light">
<div class="position-relative row w-100 h-100 bg-gredient justify-content-center">
<div class="col-lg-4 d-flex align-items-center">
<mat-card class="cardWithShadow boxed-auth">
<mat-card-content class="p-32">
<div class="text-center">
<a [routerLink]="['/dashboard']">
<img src="./assets/images/logos/dark-logo.svg" class="align-middle m-2" alt="logo" />
</a>
</div>
<div class="row m-t-24 custom-row">
<div class="col-12 col-sm-6">
<button mat-stroked-button class="w-100">
<div class="d-flex align-items-center">
<img src="/assets/images/svgs/google-icon.svg" alt="google" width="16" class="m-r-8" />
Sign in with Google
</div>
</button>
</div>
<div class="col-12 col-sm-6">
<button mat-stroked-button class="w-100 d-flex align-items-center">
<div class="d-flex align-items-center">
<img src="/assets/images/svgs/facebook-icon.svg" alt="facebook" width="40" class="m-r-4" />
Sign in with FB
</div>
</button>
</div>
</div>
<div class="or-border m-t-30">or sign up with</div>
<form class="m-t-30">
<mat-label class="f-s-14 f-w-600 m-b-12 d-block">Name</mat-label>
<mat-form-field appearance="outline" class="w-100" color="primary">
<input matInput />
</mat-form-field>
<mat-label class="f-s-14 f-w-600 m-b-12 d-block">Email Adddress</mat-label>
<mat-form-field appearance="outline" class="w-100" color="primary">
<input matInput type="email" />
</mat-form-field>
<!-- password -->
<mat-label class="f-s-14 f-w-600 m-b-12 d-block">Password</mat-label>
<mat-form-field appearance="outline" class="w-100" color="primary">
<input matInput type="password" />
</mat-form-field>
<a [routerLink]="['/']" mat-flat-button color="primary" class="w-100">
Sign Up
</a>
<!-- input -->
</form>
<span class="d-block f-w-500 text-center m-t-24">Already have an Account?
<a [routerLink]="['/authentication/login']" class="text-decoration-none text-primary f-w-500 f-s-14">
Sign In</a>
</span>
</mat-card-content>
</mat-card>
</div>
</div>
</div>

View File

@@ -1,33 +1,33 @@
import { Component } from '@angular/core';
import { CoreService } from 'src/app/services/core.service';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { MaterialModule } from 'src/app/material.module';
@Component({
selector: 'app-side-register',
imports: [RouterModule, MaterialModule, FormsModule, ReactiveFormsModule],
templateUrl: './side-register.component.html',
})
export class AppSideRegisterComponent {
options = this.settings.getOptions();
constructor(private settings: CoreService, private router: Router) {}
form = new FormGroup({
uname: new FormControl('', [Validators.required, Validators.minLength(6)]),
email: new FormControl('', [Validators.required]),
password: new FormControl('', [Validators.required]),
});
get f() {
return this.form.controls;
}
submit() {
// console.log(this.form.value);
this.router.navigate(['/']);
}
}
import { Component } from '@angular/core';
import { CoreService } from 'src/app/services/core.service';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { MaterialModule } from 'src/app/material.module';
@Component({
selector: 'app-side-register',
imports: [RouterModule, MaterialModule, FormsModule, ReactiveFormsModule],
templateUrl: './side-register.component.html',
})
export class AppSideRegisterComponent {
options = this.settings.getOptions();
constructor(private settings: CoreService, private router: Router) {}
form = new FormGroup({
uname: new FormControl('', [Validators.required, Validators.minLength(6)]),
email: new FormControl('', [Validators.required]),
password: new FormControl('', [Validators.required]),
});
get f() {
return this.form.controls;
}
submit() {
// console.log(this.form.value);
this.router.navigate(['/']);
}
}

View File

@@ -1,22 +1,22 @@
import { Routes } from '@angular/router';
// pages
import { AppIconsComponent } from './icons/icons.component';
import { AppSamplePageComponent } from './sample-page/sample-page.component';
export const ExtraRoutes: Routes = [
{
path: '',
children: [
{
path: 'icons',
component: AppIconsComponent,
},
{
path: 'sample-page',
component: AppSamplePageComponent,
},
],
},
];
import { Routes } from '@angular/router';
// pages
import { AppIconsComponent } from './icons/icons.component';
import { AppSamplePageComponent } from './sample-page/sample-page.component';
export const ExtraRoutes: Routes = [
{
path: '',
children: [
{
path: 'icons',
component: AppIconsComponent,
},
{
path: 'sample-page',
component: AppSamplePageComponent,
},
],
},
];

View File

@@ -1,12 +1,12 @@
<!-- ============================================================== -->
<!-- Simple four boxes Row -->
<!-- ============================================================== -->
<mat-card class="cardWithShadow">
<mat-card-content>
<mat-card-title>Icons</mat-card-title>
<mat-card-subtitle class="m-b-24">Tabler Icons</mat-card-subtitle>
<iframe src="https://tabler.io/icons" title="Tabler Icons" width="100%" height="630px"
allowfullscreen frameBorder="0">
</iframe>
</mat-card-content>
<!-- ============================================================== -->
<!-- Simple four boxes Row -->
<!-- ============================================================== -->
<mat-card class="cardWithShadow">
<mat-card-content>
<mat-card-title>Icons</mat-card-title>
<mat-card-subtitle class="m-b-24">Tabler Icons</mat-card-subtitle>
<iframe src="https://tabler.io/icons" title="Tabler Icons" width="100%" height="630px"
allowfullscreen frameBorder="0">
</iframe>
</mat-card-content>
</mat-card>

View File

@@ -1,9 +1,9 @@
import { Component } from '@angular/core';
import { MaterialModule } from '../../../material.module';
@Component({
selector: 'app-icons',
imports: [MaterialModule],
templateUrl: './icons.component.html',
})
export class AppIconsComponent { }
import { Component } from '@angular/core';
import { MaterialModule } from '../../../material.module';
@Component({
selector: 'app-icons',
imports: [MaterialModule],
templateUrl: './icons.component.html',
})
export class AppIconsComponent { }

View File

@@ -1,9 +1,9 @@
<!-- ============================================================== -->
<!-- Simple four boxes Row -->
<!-- ============================================================== -->
<mat-card class="cardWithShadow">
<mat-card-content>
<mat-card-title>Sample Page</mat-card-title>
<mat-card-subtitle>This is test page</mat-card-subtitle>
</mat-card-content>
<!-- ============================================================== -->
<!-- Simple four boxes Row -->
<!-- ============================================================== -->
<mat-card class="cardWithShadow">
<mat-card-content>
<mat-card-title>Sample Page</mat-card-title>
<mat-card-subtitle>This is test page</mat-card-subtitle>
</mat-card-content>
</mat-card>

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