This commit is contained in:
aiae.ceo
2026-01-29 13:16:05 +00:00
parent 9180187748
commit 302d022b54
17 changed files with 9965 additions and 385 deletions

View File

@@ -2,7 +2,8 @@
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"cli": {
"packageManager": "npm"
"packageManager": "npm",
"analytics": false
},
"newProjectRoot": "projects",
"projects": {

9304
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -36,8 +36,12 @@
"@angular/build": "^21.0.2",
"@angular/cli": "^21.0.2",
"@angular/compiler-cli": "^21.0.0",
"@tailwindcss/postcss": "^4.1.18",
"autoprefixer": "^10.4.23",
"jsdom": "^27.1.0",
"postcss": "^8.5.6",
"tailwindcss": "^4.1.18",
"typescript": "~5.9.2",
"vitest": "^4.0.8"
}
}
}

15
src/app/app.component.ts Normal file
View File

@@ -0,0 +1,15 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { RouterOutlet } from '@angular/router';
@Component({
selector: 'app-root',
imports: [RouterOutlet],
template: `
<router-outlet></router-outlet>
`,
styles: [],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent {
title = 'MaktApp';
}

View File

@@ -1,11 +1,11 @@
import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
import { ApplicationConfig, provideZonelessChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideBrowserGlobalErrorListeners(),
provideZonelessChangeDetection(),
provideRouter(routes)
]
};

View File

View File

@@ -1,342 +0,0 @@
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
<!-- * * * * * * * * * * * The content below * * * * * * * * * * * -->
<!-- * * * * * * * * * * is only a placeholder * * * * * * * * * * -->
<!-- * * * * * * * * * * and can be replaced. * * * * * * * * * * -->
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
<!-- * * * * * * * * * Delete the template below * * * * * * * * * -->
<!-- * * * * * * * to get started with your project! * * * * * * * -->
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
<style>
:host {
--bright-blue: oklch(51.01% 0.274 263.83);
--electric-violet: oklch(53.18% 0.28 296.97);
--french-violet: oklch(47.66% 0.246 305.88);
--vivid-pink: oklch(69.02% 0.277 332.77);
--hot-red: oklch(61.42% 0.238 15.34);
--orange-red: oklch(63.32% 0.24 31.68);
--gray-900: oklch(19.37% 0.006 300.98);
--gray-700: oklch(36.98% 0.014 302.71);
--gray-400: oklch(70.9% 0.015 304.04);
--red-to-pink-to-purple-vertical-gradient: linear-gradient(
180deg,
var(--orange-red) 0%,
var(--vivid-pink) 50%,
var(--electric-violet) 100%
);
--red-to-pink-to-purple-horizontal-gradient: linear-gradient(
90deg,
var(--orange-red) 0%,
var(--vivid-pink) 50%,
var(--electric-violet) 100%
);
--pill-accent: var(--bright-blue);
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
"Segoe UI Symbol";
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
h1 {
font-size: 3.125rem;
color: var(--gray-900);
font-weight: 500;
line-height: 100%;
letter-spacing: -0.125rem;
margin: 0;
font-family: "Inter Tight", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
"Segoe UI Symbol";
}
p {
margin: 0;
color: var(--gray-700);
}
main {
width: 100%;
min-height: 100%;
display: flex;
justify-content: center;
align-items: center;
padding: 1rem;
box-sizing: inherit;
position: relative;
}
.angular-logo {
max-width: 9.2rem;
}
.content {
display: flex;
justify-content: space-around;
width: 100%;
max-width: 700px;
margin-bottom: 3rem;
}
.content h1 {
margin-top: 1.75rem;
}
.content p {
margin-top: 1.5rem;
}
.divider {
width: 1px;
background: var(--red-to-pink-to-purple-vertical-gradient);
margin-inline: 0.5rem;
}
.pill-group {
display: flex;
flex-direction: column;
align-items: start;
flex-wrap: wrap;
gap: 1.25rem;
}
.pill {
display: flex;
align-items: center;
--pill-accent: var(--bright-blue);
background: color-mix(in srgb, var(--pill-accent) 5%, transparent);
color: var(--pill-accent);
padding-inline: 0.75rem;
padding-block: 0.375rem;
border-radius: 2.75rem;
border: 0;
transition: background 0.3s ease;
font-family: var(--inter-font);
font-size: 0.875rem;
font-style: normal;
font-weight: 500;
line-height: 1.4rem;
letter-spacing: -0.00875rem;
text-decoration: none;
white-space: nowrap;
}
.pill:hover {
background: color-mix(in srgb, var(--pill-accent) 15%, transparent);
}
.pill-group .pill:nth-child(6n + 1) {
--pill-accent: var(--bright-blue);
}
.pill-group .pill:nth-child(6n + 2) {
--pill-accent: var(--electric-violet);
}
.pill-group .pill:nth-child(6n + 3) {
--pill-accent: var(--french-violet);
}
.pill-group .pill:nth-child(6n + 4),
.pill-group .pill:nth-child(6n + 5),
.pill-group .pill:nth-child(6n + 6) {
--pill-accent: var(--hot-red);
}
.pill-group svg {
margin-inline-start: 0.25rem;
}
.social-links {
display: flex;
align-items: center;
gap: 0.73rem;
margin-top: 1.5rem;
}
.social-links path {
transition: fill 0.3s ease;
fill: var(--gray-400);
}
.social-links a:hover svg path {
fill: var(--gray-900);
}
@media screen and (max-width: 650px) {
.content {
flex-direction: column;
width: max-content;
}
.divider {
height: 1px;
width: 100%;
background: var(--red-to-pink-to-purple-horizontal-gradient);
margin-block: 1.5rem;
}
}
</style>
<main class="main">
<div class="content">
<div class="left-side">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 982 239"
fill="none"
class="angular-logo"
>
<g clip-path="url(#a)">
<path
fill="url(#b)"
d="M388.676 191.625h30.849L363.31 31.828h-35.758l-56.215 159.797h30.848l13.174-39.356h60.061l13.256 39.356Zm-65.461-62.675 21.602-64.311h1.227l21.602 64.311h-44.431Zm126.831-7.527v70.202h-28.23V71.839h27.002v20.374h1.392c2.782-6.71 7.2-12.028 13.255-15.956 6.056-3.927 13.584-5.89 22.503-5.89 8.264 0 15.465 1.8 21.684 5.318 6.137 3.518 10.964 8.673 14.319 15.382 3.437 6.71 5.074 14.81 4.992 24.383v76.175h-28.23v-71.92c0-8.019-2.046-14.237-6.219-18.819-4.173-4.5-9.819-6.791-17.102-6.791-4.91 0-9.328 1.063-13.174 3.272-3.846 2.128-6.792 5.237-9.001 9.328-2.046 4.009-3.191 8.918-3.191 14.728ZM589.233 239c-10.147 0-18.82-1.391-26.103-4.091-7.282-2.7-13.092-6.382-17.511-10.964-4.418-4.582-7.528-9.655-9.164-15.219l25.448-6.136c1.145 2.372 2.782 4.663 4.991 6.954 2.209 2.291 5.155 4.255 8.837 5.81 3.683 1.554 8.428 2.291 14.074 2.291 8.019 0 14.647-1.964 19.884-5.81 5.237-3.845 7.856-10.227 7.856-19.064v-22.665h-1.391c-1.473 2.946-3.601 5.892-6.383 9.001-2.782 3.109-6.464 5.645-10.965 7.691-4.582 2.046-10.228 3.109-17.101 3.109-9.165 0-17.511-2.209-25.039-6.545-7.446-4.337-13.42-10.883-17.757-19.474-4.418-8.673-6.628-19.473-6.628-32.565 0-13.091 2.21-24.301 6.628-33.383 4.419-9.082 10.311-15.955 17.839-20.7 7.528-4.746 15.874-7.037 25.039-7.037 7.037 0 12.846 1.145 17.347 3.518 4.582 2.373 8.182 5.236 10.883 8.51 2.7 3.272 4.746 6.382 6.137 9.327h1.554v-19.8h27.821v121.749c0 10.228-2.454 18.737-7.364 25.447-4.91 6.709-11.538 11.7-20.048 15.055-8.509 3.355-18.165 4.991-28.884 4.991Zm.245-71.266c5.974 0 11.047-1.473 15.302-4.337 4.173-2.945 7.446-7.118 9.573-12.519 2.21-5.482 3.274-12.027 3.274-19.637 0-7.609-1.064-14.155-3.274-19.8-2.127-5.646-5.318-10.064-9.491-13.255-4.174-3.11-9.329-4.746-15.384-4.746s-11.537 1.636-15.792 4.91c-4.173 3.272-7.365 7.772-9.492 13.418-2.128 5.727-3.191 12.191-3.191 19.392 0 7.2 1.063 13.745 3.273 19.228 2.127 5.482 5.318 9.736 9.573 12.764 4.174 3.027 9.41 4.582 15.629 4.582Zm141.56-26.51V71.839h28.23v119.786h-27.412v-21.273h-1.227c-2.7 6.709-7.119 12.191-13.338 16.446-6.137 4.255-13.747 6.382-22.748 6.382-7.855 0-14.81-1.718-20.783-5.237-5.974-3.518-10.72-8.591-14.075-15.382-3.355-6.709-5.073-14.891-5.073-24.464V71.839h28.312v71.921c0 7.609 2.046 13.664 6.219 18.083 4.173 4.5 9.655 6.709 16.365 6.709 4.173 0 8.183-.982 12.111-3.028 3.927-2.045 7.118-5.072 9.655-9.082 2.537-4.091 3.764-9.164 3.764-15.218Zm65.707-109.395v159.796h-28.23V31.828h28.23Zm44.841 162.169c-7.61 0-14.402-1.391-20.457-4.091-6.055-2.7-10.883-6.791-14.32-12.109-3.518-5.319-5.237-11.946-5.237-19.801 0-6.791 1.228-12.355 3.765-16.773 2.536-4.419 5.891-7.937 10.228-10.637 4.337-2.618 9.164-4.664 14.647-6.055 5.4-1.391 11.046-2.373 16.856-3.027 7.037-.737 12.683-1.391 17.102-1.964 4.337-.573 7.528-1.555 9.574-2.782 1.963-1.309 3.027-3.273 3.027-5.973v-.491c0-5.891-1.718-10.391-5.237-13.664-3.518-3.191-8.51-4.828-15.056-4.828-6.955 0-12.356 1.473-16.447 4.5-4.009 3.028-6.71 6.546-8.183 10.719l-26.348-3.764c2.046-7.282 5.483-13.336 10.31-18.328 4.746-4.909 10.638-8.59 17.511-11.045 6.955-2.455 14.565-3.682 22.912-3.682 5.809 0 11.537.654 17.265 2.045s10.965 3.6 15.711 6.71c4.746 3.109 8.51 7.282 11.455 12.6 2.864 5.318 4.337 11.946 4.337 19.883v80.184h-27.166v-16.446h-.9c-1.719 3.355-4.092 6.464-7.201 9.328-3.109 2.864-6.955 5.237-11.619 6.955-4.828 1.718-10.229 2.536-16.529 2.536Zm7.364-20.701c5.646 0 10.556-1.145 14.729-3.354 4.173-2.291 7.364-5.237 9.655-9.001 2.292-3.763 3.355-7.854 3.355-12.273v-14.155c-.9.737-2.373 1.391-4.5 2.046-2.128.654-4.419 1.145-7.037 1.636-2.619.491-5.155.9-7.692 1.227-2.537.328-4.746.655-6.628.901-4.173.572-8.019 1.472-11.292 2.781-3.355 1.31-5.973 3.11-7.855 5.401-1.964 2.291-2.864 5.318-2.864 8.918 0 5.237 1.882 9.164 5.728 11.782 3.682 2.782 8.51 4.091 14.401 4.091Zm64.643 18.328V71.839h27.412v19.965h1.227c2.21-6.955 5.974-12.274 11.292-16.038 5.319-3.763 11.456-5.645 18.329-5.645 1.555 0 3.355.082 5.237.163 1.964.164 3.601.328 4.91.573v25.938c-1.227-.41-3.109-.819-5.646-1.146a58.814 58.814 0 0 0-7.446-.49c-5.155 0-9.738 1.145-13.829 3.354-4.091 2.209-7.282 5.236-9.655 9.164-2.373 3.927-3.519 8.427-3.519 13.5v70.448h-28.312ZM222.077 39.192l-8.019 125.923L137.387 0l84.69 39.192Zm-53.105 162.825-57.933 33.056-57.934-33.056 11.783-28.556h92.301l11.783 28.556ZM111.039 62.675l30.357 73.803H80.681l30.358-73.803ZM7.937 165.115 0 39.192 84.69 0 7.937 165.115Z"
/>
<path
fill="url(#c)"
d="M388.676 191.625h30.849L363.31 31.828h-35.758l-56.215 159.797h30.848l13.174-39.356h60.061l13.256 39.356Zm-65.461-62.675 21.602-64.311h1.227l21.602 64.311h-44.431Zm126.831-7.527v70.202h-28.23V71.839h27.002v20.374h1.392c2.782-6.71 7.2-12.028 13.255-15.956 6.056-3.927 13.584-5.89 22.503-5.89 8.264 0 15.465 1.8 21.684 5.318 6.137 3.518 10.964 8.673 14.319 15.382 3.437 6.71 5.074 14.81 4.992 24.383v76.175h-28.23v-71.92c0-8.019-2.046-14.237-6.219-18.819-4.173-4.5-9.819-6.791-17.102-6.791-4.91 0-9.328 1.063-13.174 3.272-3.846 2.128-6.792 5.237-9.001 9.328-2.046 4.009-3.191 8.918-3.191 14.728ZM589.233 239c-10.147 0-18.82-1.391-26.103-4.091-7.282-2.7-13.092-6.382-17.511-10.964-4.418-4.582-7.528-9.655-9.164-15.219l25.448-6.136c1.145 2.372 2.782 4.663 4.991 6.954 2.209 2.291 5.155 4.255 8.837 5.81 3.683 1.554 8.428 2.291 14.074 2.291 8.019 0 14.647-1.964 19.884-5.81 5.237-3.845 7.856-10.227 7.856-19.064v-22.665h-1.391c-1.473 2.946-3.601 5.892-6.383 9.001-2.782 3.109-6.464 5.645-10.965 7.691-4.582 2.046-10.228 3.109-17.101 3.109-9.165 0-17.511-2.209-25.039-6.545-7.446-4.337-13.42-10.883-17.757-19.474-4.418-8.673-6.628-19.473-6.628-32.565 0-13.091 2.21-24.301 6.628-33.383 4.419-9.082 10.311-15.955 17.839-20.7 7.528-4.746 15.874-7.037 25.039-7.037 7.037 0 12.846 1.145 17.347 3.518 4.582 2.373 8.182 5.236 10.883 8.51 2.7 3.272 4.746 6.382 6.137 9.327h1.554v-19.8h27.821v121.749c0 10.228-2.454 18.737-7.364 25.447-4.91 6.709-11.538 11.7-20.048 15.055-8.509 3.355-18.165 4.991-28.884 4.991Zm.245-71.266c5.974 0 11.047-1.473 15.302-4.337 4.173-2.945 7.446-7.118 9.573-12.519 2.21-5.482 3.274-12.027 3.274-19.637 0-7.609-1.064-14.155-3.274-19.8-2.127-5.646-5.318-10.064-9.491-13.255-4.174-3.11-9.329-4.746-15.384-4.746s-11.537 1.636-15.792 4.91c-4.173 3.272-7.365 7.772-9.492 13.418-2.128 5.727-3.191 12.191-3.191 19.392 0 7.2 1.063 13.745 3.273 19.228 2.127 5.482 5.318 9.736 9.573 12.764 4.174 3.027 9.41 4.582 15.629 4.582Zm141.56-26.51V71.839h28.23v119.786h-27.412v-21.273h-1.227c-2.7 6.709-7.119 12.191-13.338 16.446-6.137 4.255-13.747 6.382-22.748 6.382-7.855 0-14.81-1.718-20.783-5.237-5.974-3.518-10.72-8.591-14.075-15.382-3.355-6.709-5.073-14.891-5.073-24.464V71.839h28.312v71.921c0 7.609 2.046 13.664 6.219 18.083 4.173 4.5 9.655 6.709 16.365 6.709 4.173 0 8.183-.982 12.111-3.028 3.927-2.045 7.118-5.072 9.655-9.082 2.537-4.091 3.764-9.164 3.764-15.218Zm65.707-109.395v159.796h-28.23V31.828h28.23Zm44.841 162.169c-7.61 0-14.402-1.391-20.457-4.091-6.055-2.7-10.883-6.791-14.32-12.109-3.518-5.319-5.237-11.946-5.237-19.801 0-6.791 1.228-12.355 3.765-16.773 2.536-4.419 5.891-7.937 10.228-10.637 4.337-2.618 9.164-4.664 14.647-6.055 5.4-1.391 11.046-2.373 16.856-3.027 7.037-.737 12.683-1.391 17.102-1.964 4.337-.573 7.528-1.555 9.574-2.782 1.963-1.309 3.027-3.273 3.027-5.973v-.491c0-5.891-1.718-10.391-5.237-13.664-3.518-3.191-8.51-4.828-15.056-4.828-6.955 0-12.356 1.473-16.447 4.5-4.009 3.028-6.71 6.546-8.183 10.719l-26.348-3.764c2.046-7.282 5.483-13.336 10.31-18.328 4.746-4.909 10.638-8.59 17.511-11.045 6.955-2.455 14.565-3.682 22.912-3.682 5.809 0 11.537.654 17.265 2.045s10.965 3.6 15.711 6.71c4.746 3.109 8.51 7.282 11.455 12.6 2.864 5.318 4.337 11.946 4.337 19.883v80.184h-27.166v-16.446h-.9c-1.719 3.355-4.092 6.464-7.201 9.328-3.109 2.864-6.955 5.237-11.619 6.955-4.828 1.718-10.229 2.536-16.529 2.536Zm7.364-20.701c5.646 0 10.556-1.145 14.729-3.354 4.173-2.291 7.364-5.237 9.655-9.001 2.292-3.763 3.355-7.854 3.355-12.273v-14.155c-.9.737-2.373 1.391-4.5 2.046-2.128.654-4.419 1.145-7.037 1.636-2.619.491-5.155.9-7.692 1.227-2.537.328-4.746.655-6.628.901-4.173.572-8.019 1.472-11.292 2.781-3.355 1.31-5.973 3.11-7.855 5.401-1.964 2.291-2.864 5.318-2.864 8.918 0 5.237 1.882 9.164 5.728 11.782 3.682 2.782 8.51 4.091 14.401 4.091Zm64.643 18.328V71.839h27.412v19.965h1.227c2.21-6.955 5.974-12.274 11.292-16.038 5.319-3.763 11.456-5.645 18.329-5.645 1.555 0 3.355.082 5.237.163 1.964.164 3.601.328 4.91.573v25.938c-1.227-.41-3.109-.819-5.646-1.146a58.814 58.814 0 0 0-7.446-.49c-5.155 0-9.738 1.145-13.829 3.354-4.091 2.209-7.282 5.236-9.655 9.164-2.373 3.927-3.519 8.427-3.519 13.5v70.448h-28.312ZM222.077 39.192l-8.019 125.923L137.387 0l84.69 39.192Zm-53.105 162.825-57.933 33.056-57.934-33.056 11.783-28.556h92.301l11.783 28.556ZM111.039 62.675l30.357 73.803H80.681l30.358-73.803ZM7.937 165.115 0 39.192 84.69 0 7.937 165.115Z"
/>
</g>
<defs>
<radialGradient
id="c"
cx="0"
cy="0"
r="1"
gradientTransform="rotate(118.122 171.182 60.81) scale(205.794)"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#FF41F8" />
<stop offset=".707" stop-color="#FF41F8" stop-opacity=".5" />
<stop offset="1" stop-color="#FF41F8" stop-opacity="0" />
</radialGradient>
<linearGradient
id="b"
x1="0"
x2="982"
y1="192"
y2="192"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#F0060B" />
<stop offset="0" stop-color="#F0070C" />
<stop offset=".526" stop-color="#CC26D5" />
<stop offset="1" stop-color="#7702FF" />
</linearGradient>
<clipPath id="a"><path fill="#fff" d="M0 0h982v239H0z" /></clipPath>
</defs>
</svg>
<h1>Hello, {{ title() }}</h1>
<p>Congratulations! Your app is running. 🎉</p>
</div>
<div class="divider" role="separator" aria-label="Divider"></div>
<div class="right-side">
<div class="pill-group">
@for (item of [
{ title: 'Explore the Docs', link: 'https://angular.dev' },
{ title: 'Learn with Tutorials', link: 'https://angular.dev/tutorials' },
{ title: 'Prompt and best practices for AI', link: 'https://angular.dev/ai/develop-with-ai'},
{ title: 'CLI Docs', link: 'https://angular.dev/tools/cli' },
{ title: 'Angular Language Service', link: 'https://angular.dev/tools/language-service' },
{ title: 'Angular DevTools', link: 'https://angular.dev/tools/devtools' },
]; track item.title) {
<a
class="pill"
[href]="item.link"
target="_blank"
rel="noopener"
>
<span>{{ item.title }}</span>
<svg
xmlns="http://www.w3.org/2000/svg"
height="14"
viewBox="0 -960 960 960"
width="14"
fill="currentColor"
>
<path
d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h560v-280h80v280q0 33-23.5 56.5T760-120H200Zm188-212-56-56 372-372H560v-80h280v280h-80v-144L388-332Z"
/>
</svg>
</a>
}
</div>
<div class="social-links">
<a
href="https://github.com/angular/angular"
aria-label="Github"
target="_blank"
rel="noopener"
>
<svg
width="25"
height="24"
viewBox="0 0 25 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
alt="Github"
>
<path
d="M12.3047 0C5.50634 0 0 5.50942 0 12.3047C0 17.7423 3.52529 22.3535 8.41332 23.9787C9.02856 24.0946 9.25414 23.7142 9.25414 23.3871C9.25414 23.0949 9.24389 22.3207 9.23876 21.2953C5.81601 22.0377 5.09414 19.6444 5.09414 19.6444C4.53427 18.2243 3.72524 17.8449 3.72524 17.8449C2.61064 17.082 3.81137 17.0973 3.81137 17.0973C5.04697 17.1835 5.69604 18.3647 5.69604 18.3647C6.79321 20.2463 8.57636 19.7029 9.27978 19.3881C9.39052 18.5924 9.70736 18.0499 10.0591 17.7423C7.32641 17.4347 4.45429 16.3765 4.45429 11.6618C4.45429 10.3185 4.9311 9.22133 5.72065 8.36C5.58222 8.04931 5.16694 6.79833 5.82831 5.10337C5.82831 5.10337 6.85883 4.77319 9.2121 6.36459C10.1965 6.09082 11.2424 5.95546 12.2883 5.94931C13.3342 5.95546 14.3801 6.09082 15.3644 6.36459C17.7023 4.77319 18.7328 5.10337 18.7328 5.10337C19.3942 6.79833 18.9789 8.04931 18.8559 8.36C19.6403 9.22133 20.1171 10.3185 20.1171 11.6618C20.1171 16.3888 17.2409 17.4296 14.5031 17.7321C14.9338 18.1012 15.3337 18.8559 15.3337 20.0084C15.3337 21.6552 15.3183 22.978 15.3183 23.3779C15.3183 23.7009 15.5336 24.0854 16.1642 23.9623C21.0871 22.3484 24.6094 17.7341 24.6094 12.3047C24.6094 5.50942 19.0999 0 12.3047 0Z"
/>
</svg>
</a>
<a
href="https://x.com/angular"
aria-label="X"
target="_blank"
rel="noopener"
>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
alt="X"
>
<path
d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"
/>
</svg>
</a>
<a
href="https://www.youtube.com/channel/UCbn1OgGei-DV7aSRo_HaAiw"
aria-label="Youtube"
target="_blank"
rel="noopener"
>
<svg
width="29"
height="20"
viewBox="0 0 29 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
alt="Youtube"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M27.4896 1.52422C27.9301 1.96749 28.2463 2.51866 28.4068 3.12258C29.0004 5.35161 29.0004 10 29.0004 10C29.0004 10 29.0004 14.6484 28.4068 16.8774C28.2463 17.4813 27.9301 18.0325 27.4896 18.4758C27.0492 18.9191 26.5 19.2389 25.8972 19.4032C23.6778 20 14.8068 20 14.8068 20C14.8068 20 5.93586 20 3.71651 19.4032C3.11363 19.2389 2.56449 18.9191 2.12405 18.4758C1.68361 18.0325 1.36732 17.4813 1.20683 16.8774C0.613281 14.6484 0.613281 10 0.613281 10C0.613281 10 0.613281 5.35161 1.20683 3.12258C1.36732 2.51866 1.68361 1.96749 2.12405 1.52422C2.56449 1.08095 3.11363 0.76113 3.71651 0.596774C5.93586 0 14.8068 0 14.8068 0C14.8068 0 23.6778 0 25.8972 0.596774C26.5 0.76113 27.0492 1.08095 27.4896 1.52422ZM19.3229 10L11.9036 5.77905V14.221L19.3229 10Z"
/>
</svg>
</a>
</div>
</div>
</div>
</main>
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
<!-- * * * * * * * * * * * The content above * * * * * * * * * * * * -->
<!-- * * * * * * * * * * is only a placeholder * * * * * * * * * * * -->
<!-- * * * * * * * * * * and can be replaced. * * * * * * * * * * * -->
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
<!-- * * * * * * * * * * End of Placeholder * * * * * * * * * * * * -->
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
<router-outlet />

View File

@@ -1,3 +1,7 @@
import { Routes } from '@angular/router';
import { StudentDashboardComponent } from './student-dashboard.component';
export const routes: Routes = [];
export const routes: Routes = [
{ path: '', component: StudentDashboardComponent },
{ path: '**', redirectTo: '' }
];

View File

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

View File

@@ -1,12 +0,0 @@
import { Component, signal } from '@angular/core';
import { RouterOutlet } from '@angular/router';
@Component({
selector: 'app-root',
imports: [RouterOutlet],
templateUrl: './app.html',
styleUrl: './app.css'
})
export class App {
protected readonly title = signal('myapp');
}

View File

@@ -0,0 +1,102 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { CommonModule, DatePipe, PercentPipe } from '@angular/common';
import { DataService } from './data.service';
@Component({
selector: 'app-courses',
imports: [CommonModule],
template: `
<div class="space-y-6">
<div class="flex justify-between items-end px-1 mb-2">
<div>
<h2 class="text-3xl font-extrabold text-slate-800 tracking-tight">My Courses</h2>
<p class="text-sm text-slate-400 font-semibold mt-1">Continue where you left off</p>
</div>
<a href="#" class="text-sm font-bold text-indigo-600 hover:text-indigo-700 transition-colors flex items-center gap-1 group">
View All
<span class="group-hover:translate-x-1 transition-transform">→</span>
</a>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
@for (course of courses(); track course.id) {
<div class="group glass-card rounded-3xl p-3 hover:shadow-glow-primary transition-all duration-300 hover:-translate-y-1 flex flex-col h-full bg-white/60">
<!-- Thumbnail -->
<div class="relative aspect-video rounded-2xl overflow-hidden bg-slate-100 shadow-inner">
<img
[src]="course.thumbnail"
[alt]="course.title"
loading="lazy"
class="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110"
>
<!-- Overlay Gradient -->
<div class="absolute inset-0 bg-gradient-to-t from-slate-900/90 via-slate-900/20 to-transparent opacity-90"></div>
<!-- Badges -->
<div class="absolute top-3 right-3 flex gap-2">
<span class="text-[10px] font-bold text-white bg-black/40 backdrop-blur-md px-3 py-1.5 rounded-full border border-white/10 shadow-lg">
{{ course.lessonsCompleted }}/{{ course.totalLessons }} Lessons
</span>
</div>
<!-- Next Lesson Teaser on Image -->
<div class="absolute bottom-4 left-4 right-4">
<p class="text-[10px] uppercase tracking-widest text-indigo-200 font-bold mb-1">Up Next</p>
<p class="text-sm font-bold text-white truncate drop-shadow-md">{{ course.nextLesson }}</p>
</div>
</div>
<!-- Content -->
<div class="pt-5 px-2 flex-grow flex flex-col">
<h3 class="font-extrabold text-xl text-slate-800 mb-5 line-clamp-1 group-hover:text-transparent group-hover:bg-clip-text group-hover:bg-gradient-to-r group-hover:from-violet-600 group-hover:to-indigo-600 transition-colors" [title]="course.title">{{ course.title }}</h3>
<div class="mt-auto space-y-5">
<!-- Detailed Progress -->
<div>
<div class="flex justify-between text-xs font-bold mb-2">
<span class="text-slate-400">Progress</span>
<span class="text-slate-700">{{ course.progress | percent }}</span>
</div>
<div class="relative w-full bg-slate-100 rounded-full h-2.5 overflow-hidden shadow-inner">
<div
class="absolute top-0 left-0 h-full bg-gradient-to-r from-violet-500 via-fuchsia-500 to-indigo-500 rounded-full transition-all duration-1000 ease-out shadow-[0_0_12px_rgba(167,139,250,0.6)]"
[style.width]="(course.progress * 100) + '%'">
<!-- Shimmer effect -->
<div class="absolute top-0 right-0 bottom-0 w-full bg-gradient-to-r from-transparent via-white/30 to-transparent -translate-x-full animate-[shimmer_2s_infinite]"></div>
</div>
</div>
</div>
<button class="w-full py-3.5 bg-slate-50 text-slate-600 text-sm font-bold rounded-2xl border border-slate-100 hover:bg-slate-900 hover:text-white hover:border-slate-900 hover:shadow-lg hover:shadow-indigo-500/20 transition-all active:scale-95 flex items-center justify-center gap-2 group/btn">
Continue Learning
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 transition-transform group-hover/btn:translate-x-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3" />
</svg>
</button>
</div>
</div>
</div>
}
</div>
<button class="w-full py-5 border-2 border-dashed border-slate-300 rounded-3xl text-slate-400 font-bold hover:border-indigo-400 hover:text-indigo-600 hover:bg-indigo-50/50 transition-all flex items-center justify-center gap-3 group relative overflow-hidden">
<span class="w-10 h-10 rounded-full bg-slate-100 group-hover:bg-indigo-100 flex items-center justify-center transition-colors shadow-sm">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
</svg>
</span>
<span class="text-lg">Enroll in New Course</span>
</button>
</div>
`,
styles: [`
@keyframes shimmer {
100% { transform: translateX(100%); }
}
`],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CoursesComponent {
private dataService = inject(DataService);
courses = this.dataService.courses;
}

98
src/app/data.service.ts Normal file
View File

@@ -0,0 +1,98 @@
import { Injectable, signal } from '@angular/core';
import { Course, User, Wallet } from './models';
import { of } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class DataService {
// Mock User Data
readonly currentUser = signal<User>({
uid: 'user123',
displayName: 'Student Name',
photoURL: 'https://ui-avatars.com/api/?name=Student+Name&background=0D8ABC&color=fff',
activeCoursesCount: 3,
completionRate: 85
});
// Mock Courses Data
readonly courses = signal<Course[]>([
{
id: 'c1',
title: 'Python Basics',
thumbnail: 'https://placehold.co/600x400/1e293b/ffffff?text=Python',
progress: 0.45,
lessonsCompleted: 9,
totalLessons: 20,
nextLesson: 'Variables and Data Types'
},
{
id: 'c2',
title: 'Angular Fundamentals',
thumbnail: 'https://placehold.co/600x400/dd0031/ffffff?text=Angular',
progress: 0.1,
lessonsCompleted: 2,
totalLessons: 20,
nextLesson: 'Components & Templates'
},
{
id: 'c3',
title: 'Web Design 101',
thumbnail: 'https://placehold.co/600x400/3b82f6/ffffff?text=Web+Design',
progress: 0.75,
lessonsCompleted: 15,
totalLessons: 20,
nextLesson: 'Responsive Layouts'
},
{
id: 'c4',
title: 'Data Science Intro',
thumbnail: 'https://placehold.co/600x400/10b981/ffffff?text=Data+Science',
progress: 0,
lessonsCompleted: 0,
totalLessons: 15,
nextLesson: 'Introduction'
}
]);
// Mock Wallet Data
readonly wallet = signal<Wallet>({
balance: 150,
currency: 'TJS',
transactions: [
{
id: 't1',
date: new Date('2024-05-20'),
type: 'top-up',
amount: 200,
status: 'completed'
},
{
id: 't2',
date: new Date('2024-05-18'),
type: 'purchase',
amount: -50,
status: 'completed'
},
{
id: 't3',
date: new Date('2024-05-10'),
type: 'purchase',
amount: -100,
status: 'completed'
}
]
});
constructor() { }
// Methods to simulate async data fetching if needed, but signals are fine for now
getMockCourses() {
return of(this.courses());
}
getMockWallet() {
return of(this.wallet());
}
}

31
src/app/models.ts Normal file
View File

@@ -0,0 +1,31 @@
export interface Course {
id: string;
title: string;
thumbnail: string;
progress: number; // 0 to 1
lessonsCompleted: number;
totalLessons: number;
nextLesson: string;
}
export interface Transaction {
id: string;
date: Date;
type: 'top-up' | 'purchase' | 'withdrawal';
amount: number;
status: 'completed' | 'pending' | 'failed';
}
export interface Wallet {
balance: number;
currency: string;
transactions: Transaction[];
}
export interface User {
uid: string;
displayName: string;
photoURL?: string;
activeCoursesCount: number;
completionRate: number;
}

View File

@@ -0,0 +1,170 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { CoursesComponent } from './courses.component';
import { WalletComponent } from './wallet.component';
import { DataService } from './data.service';
@Component({
selector: 'app-student-dashboard',
imports: [CoursesComponent, WalletComponent],
template: `
<div class="min-h-screen flex flex-col relative overflow-hidden">
<!-- Background Shapes -->
<div class="fixed top-0 left-0 w-full h-full overflow-hidden -z-10 pointer-events-none">
<div class="absolute top-[-10%] left-[-10%] w-[40%] h-[40%] bg-violet-400/20 rounded-full blur-[120px] mix-blend-multiply animate-blob"></div>
<div class="absolute top-[20%] right-[-10%] w-[35%] h-[35%] bg-indigo-400/20 rounded-full blur-[120px] mix-blend-multiply animate-blob animation-delay-2000"></div>
<div class="absolute bottom-[-10%] left-[20%] w-[40%] h-[40%] bg-fuchsia-400/20 rounded-full blur-[120px] mix-blend-multiply animate-blob animation-delay-4000"></div>
</div>
<!-- Header -->
<header class="fixed top-0 left-0 right-0 z-50 glass-panel border-b border-white/40 shadow-sm transition-all duration-300">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-20 flex items-center justify-between">
<div class="flex items-center gap-4">
<!-- Mobile Menu -->
<button class="lg:hidden p-2 text-slate-500 hover:bg-slate-100/50 rounded-xl transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8h16M4 16h16" />
</svg>
</button>
<div class="flex flex-col">
<span class="text-2xl font-black bg-gradient-to-r from-violet-600 via-fuchsia-600 to-indigo-600 bg-clip-text text-transparent tracking-tighter drop-shadow-sm">MaktApp</span>
</div>
</div>
<div class="flex items-center gap-6">
<!-- Notification Bell -->
<button class="relative group p-2 rounded-full hover:bg-slate-100/50 transition-colors">
<span class="absolute top-2 right-2.5 w-2 h-2 bg-red-500 rounded-full ring-2 ring-white shadow-sm animate-pulse"></span>
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-slate-400 group-hover:text-indigo-600 transition-colors" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
</svg>
</button>
<!-- Avatar -->
<div class="flex items-center gap-3 pl-6 border-l border-slate-200/60 cursor-pointer group">
<div class="text-right hidden sm:block">
<p class="text-sm font-bold text-slate-800 group-hover:text-indigo-600 transition-colors">{{ user().displayName }}</p>
<p class="text-xs text-slate-500 font-medium">Student</p>
</div>
<div class="relative">
<div class="absolute inset-0 bg-gradient-to-tr from-violet-500 to-fuchsia-500 rounded-full blur-[2px] opacity-0 group-hover:opacity-100 transition-opacity"></div>
<img [src]="user().photoURL" alt="Profile" class="relative w-11 h-11 rounded-full ring-2 ring-white shadow-md object-cover">
</div>
</div>
</div>
</div>
</header>
<!-- Main Content -->
<main class="flex-grow w-full max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 mt-24">
<!-- Hero Section -->
<div class="relative glass-card rounded-3xl p-8 mb-10 overflow-hidden shadow-xl shadow-indigo-100/50 ring-1 ring-white/60 animate-fade-in-up">
<!-- Decorative Gradients -->
<div class="absolute top-0 right-0 w-[600px] h-[600px] bg-gradient-to-br from-violet-100/60 to-fuchsia-100/40 rounded-full blur-3xl -mr-32 -mt-32 pointer-events-none"></div>
<div class="relative z-10 flex flex-col md:flex-row md:items-center justify-between gap-8">
<div class="max-w-2xl">
<h1 class="text-5xl font-extrabold text-slate-900 mb-4 tracking-tight leading-tight">
Welcome back, <br>
<span class="text-gradient-primary">{{ user().displayName }}!</span>
</h1>
<p class="text-slate-500 text-lg leading-relaxed mb-8 font-medium">
You're crushing your goals! You've completed <span class="font-bold text-slate-800">85%</span> of your weekly targets.
</p>
<div class="flex flex-wrap items-center gap-6">
<!-- Stat 1 -->
<div class="flex items-center gap-4 bg-white/70 backdrop-blur-md p-4 pr-8 rounded-2xl border border-white/60 shadow-sm hover:shadow-md transition-shadow">
<div class="w-14 h-14 rounded-2xl bg-gradient-to-br from-violet-500 to-indigo-600 text-white flex items-center justify-center shadow-lg shadow-indigo-500/30">
<svg xmlns="http://www.w3.org/2000/svg" class="h-7 w-7" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
</svg>
</div>
<div>
<p class="text-3xl font-bold text-slate-800 leading-none">{{ user().activeCoursesCount }}</p>
<p class="text-xs font-bold text-slate-400 uppercase tracking-wider mt-1">Active Courses</p>
</div>
</div>
<!-- Stat 2 -->
<div class="flex items-center gap-4 bg-white/70 backdrop-blur-md p-4 pr-8 rounded-2xl border border-white/60 shadow-sm hover:shadow-md transition-shadow">
<div class="w-14 h-14 rounded-2xl bg-gradient-to-br from-emerald-400 to-teal-500 text-white flex items-center justify-center shadow-lg shadow-emerald-500/30">
<svg xmlns="http://www.w3.org/2000/svg" class="h-7 w-7" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div>
<p class="text-3xl font-bold text-slate-800 leading-none">{{ user().completionRate }}%</p>
<p class="text-xs font-bold text-slate-400 uppercase tracking-wider mt-1">Avg. Score</p>
</div>
</div>
</div>
</div>
<button class="relative group overflow-hidden bg-slate-900 text-white px-10 py-5 rounded-2xl font-bold shadow-2xl shadow-slate-900/30 transition-all hover:-translate-y-1 hover:shadow-slate-900/50 active:translate-y-0 active:scale-95 z-20">
<div class="absolute inset-0 bg-gradient-to-r from-violet-600 to-indigo-600 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
<span class="relative flex items-center gap-3 text-lg">
Resume Learning
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 transition-transform group-hover:translate-x-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3" />
</svg>
</span>
</button>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8 items-start">
<!-- Courses Section (Left) -->
<div class="lg:col-span-2 w-full animate-fade-in-up delay-100">
<app-courses />
</div>
<!-- Wallet Section (Right) -->
<div class="lg:col-span-1 w-full sticky top-28 animate-fade-in-up delay-200">
<app-wallet />
</div>
</div>
</main>
<!-- Mobile Bottom Nav -->
<nav class="md:hidden fixed bottom-6 left-6 right-6 glass-panel rounded-3xl shadow-2xl border border-white/60 flex justify-around p-4 z-50 ring-1 ring-black/5">
<button class="flex flex-col items-center gap-1 text-indigo-600 relative">
<div class="absolute -top-14 opacity-0 scale-50 transition-all duration-300 pointer-events-none">
<span class="px-3 py-1.5 bg-slate-800 text-white text-xs font-bold rounded-lg shadow-lg">Home</span>
</div>
<div class="p-2 bg-indigo-50 rounded-xl">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 drop-shadow-sm" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
</div>
</button>
<button class="flex flex-col items-center gap-1 text-slate-400 hover:text-slate-600 transition-colors p-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
</svg>
</button>
<button class="flex flex-col items-center gap-1 text-slate-400 hover:text-slate-600 transition-colors p-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" />
</svg>
</button>
<button class="flex flex-col items-center gap-1 text-slate-400 hover:text-slate-600 transition-colors p-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
</button>
</nav>
<!-- Footer -->
<footer class="mt-auto py-10 border-t border-slate-200/60 bg-white/40 backdrop-blur-md">
<div class="max-w-7xl mx-auto px-4 text-center">
<p class="text-sm font-semibold text-slate-400">© 2024 MaktApp. Crafted with ❤️ for students.</p>
</div>
</footer>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class StudentDashboardComponent {
private dataService = inject(DataService);
user = this.dataService.currentUser;
}

133
src/app/wallet.component.ts Normal file
View File

@@ -0,0 +1,133 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { CommonModule, CurrencyPipe, DatePipe } from '@angular/common';
import { DataService } from './data.service';
@Component({
selector: 'app-wallet',
imports: [CommonModule],
template: `
<div class="space-y-8">
<div class="flex justify-between items-end px-1">
<div>
<h2 class="text-3xl font-extrabold text-slate-800 tracking-tight">My Wallet</h2>
<p class="text-sm text-slate-400 font-semibold mt-1">Manage your funds</p>
</div>
</div>
<!-- Balance Card -->
<div class="relative overflow-hidden rounded-[2rem] p-8 text-white shadow-2xl shadow-indigo-500/30 group cursor-pointer transition-transform hover:scale-[1.02] ring-1 ring-white/20">
<!-- Live Gradient Background -->
<div class="absolute inset-0 bg-gradient-to-br from-indigo-900 via-violet-900 to-fuchsia-900 z-0"></div>
<div class="absolute top-[-50%] right-[-20%] w-[80%] h-[80%] bg-indigo-500 rounded-full blur-[100px] opacity-40 group-hover:opacity-60 transition-opacity duration-700 mix-blend-screen animate-pulse"></div>
<div class="absolute bottom-[-20%] left-[-20%] w-[60%] h-[60%] bg-fuchsia-600 rounded-full blur-[80px] opacity-30 group-hover:opacity-50 transition-opacity duration-700 mix-blend-screen"></div>
<!-- Texture -->
<div class="absolute inset-0 opacity-20" style="background-image: radial-gradient(#ffffff 1px, transparent 1px); background-size: 20px 20px;"></div>
<!-- Content -->
<div class="relative z-10 flex flex-col h-full justify-between min-h-[180px]">
<div class="flex justify-between items-start">
<div class="p-2.5 bg-white/10 backdrop-blur-xl rounded-2xl border border-white/20 shadow-lg">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" />
</svg>
</div>
<span class="text-xs font-bold bg-emerald-500/20 text-emerald-200 border border-emerald-400/30 px-3 py-1.5 rounded-full flex items-center gap-1 backdrop-blur-md shadow-sm">
<svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6" />
</svg>
+2.5%
</span>
</div>
<div>
<p class="text-indigo-200 text-sm font-bold mb-1 tracking-widest uppercase opacity-80">Total Balance</p>
<div class="flex items-baseline gap-2">
<span class="text-5xl font-black tracking-tighter drop-shadow-lg">{{ wallet().balance }}</span>
<span class="text-xl font-bold text-indigo-300">{{ wallet().currency }}</span>
</div>
</div>
</div>
</div>
<!-- Quick Actions -->
<div class="grid grid-cols-3 gap-4">
<button class="flex flex-col items-center justify-center gap-3 p-5 bg-white/70 backdrop-blur-sm border border-white/60 rounded-[1.5rem] shadow-sm hover:shadow-xl hover:shadow-emerald-100 hover:-translate-y-1 transition-all duration-300 group">
<div class="w-14 h-14 rounded-2xl bg-gradient-to-br from-emerald-50 to-emerald-100 text-emerald-600 flex items-center justify-center shadow-inner group-hover:scale-110 transition-transform duration-300 ring-1 ring-emerald-200/50">
<svg xmlns="http://www.w3.org/2000/svg" class="h-7 w-7" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
</svg>
</div>
<span class="text-xs font-bold text-slate-600 group-hover:text-emerald-700 tracking-wide">Top Up</span>
</button>
<button class="flex flex-col items-center justify-center gap-3 p-5 bg-white/70 backdrop-blur-sm border border-white/60 rounded-[1.5rem] shadow-sm hover:shadow-xl hover:shadow-rose-100 hover:-translate-y-1 transition-all duration-300 group">
<div class="w-14 h-14 rounded-2xl bg-gradient-to-br from-rose-50 to-rose-100 text-rose-600 flex items-center justify-center shadow-inner group-hover:scale-110 transition-transform duration-300 ring-1 ring-rose-200/50">
<svg xmlns="http://www.w3.org/2000/svg" class="h-7 w-7" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" />
</svg>
</div>
<span class="text-xs font-bold text-slate-600 group-hover:text-rose-700 tracking-wide">Withdraw</span>
</button>
<button class="flex flex-col items-center justify-center gap-3 p-5 bg-white/70 backdrop-blur-sm border border-white/60 rounded-[1.5rem] shadow-sm hover:shadow-xl hover:shadow-indigo-100 hover:-translate-y-1 transition-all duration-300 group">
<div class="w-14 h-14 rounded-2xl bg-gradient-to-br from-indigo-50 to-indigo-100 text-indigo-600 flex items-center justify-center shadow-inner group-hover:scale-110 transition-transform duration-300 ring-1 ring-indigo-200/50">
<svg xmlns="http://www.w3.org/2000/svg" class="h-7 w-7" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<span class="text-xs font-bold text-slate-600 group-hover:text-indigo-700 tracking-wide">History</span>
</button>
</div>
<!-- Recent Transactions -->
<div class="glass-card rounded-[2rem] p-1.5 shadow-lg shadow-indigo-100/40 bg-white/60">
<div class="px-6 py-5 border-b border-slate-100 flex justify-between items-center">
<h3 class="text-xs font-black text-slate-400 uppercase tracking-widest">Recent Transactions</h3>
<button class="text-xs font-bold text-indigo-500 hover:text-indigo-700 transition-colors">VIEW ALL</button>
</div>
<div class="divide-y divide-slate-100/60">
@for (tx of wallet().transactions; track tx.id) {
<div class="px-6 py-4 flex items-center justify-between hover:bg-white/60 transition-colors first:rounded-t-none last:rounded-b-[1.7rem] group cursor-pointer">
<div class="flex items-center gap-5">
<div class="w-12 h-12 rounded-2xl flex items-center justify-center border transition-all duration-300 shadow-sm group-hover:shadow-md"
[class.bg-emerald-50]="tx.type === 'top-up'"
[class.border-emerald-100]="tx.type === 'top-up'"
[class.text-emerald-600]="tx.type === 'top-up'"
[class.bg-rose-50]="tx.type !== 'top-up'"
[class.border-rose-100]="tx.type !== 'top-up'"
[class.text-rose-600]="tx.type !== 'top-up'"
>
@if (tx.type === 'top-up') {
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 11l5-5m0 0l5 5m-5-5v12" />
</svg>
} @else {
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 13l-5 5m0 0l-5-5m5 5V6" />
</svg>
}
</div>
<div>
<p class="text-sm font-extrabold text-slate-800 capitalize group-hover:text-indigo-600 transition-colors">{{ tx.type }}</p>
<p class="text-xs text-slate-400 font-semibold mt-0.5">{{ tx.date | date:'MMM d, h:mm a' }}</p>
</div>
</div>
<span class="text-base font-bold tabular-nums tracking-tight"
[class.text-emerald-600]="tx.amount > 0"
[class.text-slate-800]="tx.amount <= 0"
>
{{ tx.amount > 0 ? '+' : '' }}{{ tx.amount }} {{ wallet().currency }}
</span>
</div>
}
</div>
</div>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class WalletComponent {
private dataService = inject(DataService);
wallet = this.dataService.wallet;
}

View File

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

View File

@@ -1 +1,96 @@
/* You can add global styles to this file, and also import other style files */
/* Import Fonts */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Outfit:wght@400;500;700;800&display=swap');
@import "tailwindcss";
:root {
--color-slate-50: #F8FAFC;
--color-slate-800: #1E293B;
--color-slate-500: #64748B;
--font-sans: "Outfit", "Inter", system-ui, sans-serif;
}
/* Custom Styles */
body {
font-family: var(--font-sans);
background-color: #f8fafc;
color: #0f172a;
/* Subtle Noise Texture */
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)' opacity='0.04'/%3E%3C/svg%3E");
background-attachment: fixed;
overflow-x: hidden;
}
/* Custom Scrollbar */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
}
/* Utility Classes */
.glass-panel {
background: rgba(255, 255, 255, 0.65);
backdrop-filter: blur(16px) saturate(180%);
-webkit-backdrop-filter: blur(16px) saturate(180%);
border: 1px solid rgba(255, 255, 255, 0.5);
}
.glass-card {
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.6);
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.03);
}
.text-gradient-primary {
background: linear-gradient(135deg, #6366f1 0%, #a855f7 50%, #ec4899 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.shadow-glow-primary {
box-shadow: 0 0 20px rgba(99, 102, 241, 0.35);
}
.animate-fade-in-up {
animation: fadeInUp 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards;
opacity: 0;
transform: translateY(20px);
}
.animate-blob {
animation: blob 7s infinite;
}
.delay-100 { animation-delay: 100ms; }
.delay-200 { animation-delay: 200ms; }
.delay-300 { animation-delay: 300ms; }
@keyframes fadeInUp {
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes blob {
0% { transform: translate(0px, 0px) scale(1); }
33% { transform: translate(30px, -50px) scale(1.1); }
66% { transform: translate(-20px, 20px) scale(0.9); }
100% { transform: translate(0px, 0px) scale(1); }
}