| @@ -9,7 +9,8 @@ | |||
| "test": "ng test", | |||
| "camera-server": "node dist/lot-web-ui/server/server.js", | |||
| "prettier:write": "prettier --write src", | |||
| "prettier:check": "prettier --check src" | |||
| "prettier:check": "prettier --check src", | |||
| "sprite": "svg-sprite --config sprite.config.json src/assets/images/svg/*.svg" | |||
| }, | |||
| "private": true, | |||
| "dependencies": { | |||
| @@ -27,6 +28,7 @@ | |||
| "@asymmetrik/ngx-leaflet": "^16.0.1", | |||
| "@asymmetrik/ngx-leaflet-markercluster": "^16.0.0", | |||
| "@types/leaflet.markercluster": "^1.5.4", | |||
| "aos": "^2.3.4", | |||
| "express": "^4.19.2", | |||
| "express-ws": "^5.0.2", | |||
| "leaflet": "^1.9.4", | |||
| @@ -34,10 +36,12 @@ | |||
| "moment": "^2.30.1", | |||
| "ngx-mqtt": "^17.0.0", | |||
| "ngx-toastr": "^17.0.2", | |||
| "ngx-webstorage": "^12.0.0", | |||
| "prettier": "^3.3.3", | |||
| "rtsp-relay": "^1.8.0", | |||
| "rxjs": "~7.8.0", | |||
| "rxjs-websockets": "^9.0.0", | |||
| "svg-sprite": "^2.0.4", | |||
| "tslib": "^2.3.0", | |||
| "zone.js": "~0.13.0" | |||
| }, | |||
| @@ -45,14 +49,18 @@ | |||
| "@angular-devkit/build-angular": "^16.2.14", | |||
| "@angular/cli": "^16.2.14", | |||
| "@angular/compiler-cli": "^16.2.0", | |||
| "@types/aos": "^3.0.7", | |||
| "@types/jasmine": "~4.3.0", | |||
| "@types/leaflet": "^1.9.12", | |||
| "autoprefixer": "^10.4.20", | |||
| "jasmine-core": "~4.6.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", | |||
| "postcss": "^8.4.47", | |||
| "tailwindcss": "^3.4.14", | |||
| "typescript": "~5.1.3" | |||
| } | |||
| } | |||
| @@ -0,0 +1,9 @@ | |||
| { | |||
| "mode": { | |||
| "symbol": { | |||
| "dest": "src/assets/images/svg/sprite", | |||
| "sprite": "sprite.svg", | |||
| "prefix": "svg-%s" | |||
| } | |||
| } | |||
| } | |||
| @@ -1,12 +1,23 @@ | |||
| import { NgModule } from '@angular/core'; | |||
| import { RouterModule, Routes } from '@angular/router'; | |||
| import { LayoutComponent } from './shared/component/layout/layout.component'; | |||
| import { authGuard } from './core/guards/auth.guard'; | |||
| import { RouteUtils } from './shared/utils/route.utils'; | |||
| import { unAuthGuard } from './core/guards/unAuth.guard'; | |||
| const routes: Routes = [ | |||
| { path: '', redirectTo: '/homepage', pathMatch: 'full' }, | |||
| { | |||
| path: RouteUtils.Session.LOGIN, | |||
| loadComponent: () => | |||
| import('./modules/session/session.component').then( | |||
| (m) => m.SessionComponent, | |||
| ), | |||
| canActivate: [unAuthGuard], | |||
| }, | |||
| { | |||
| path: '', | |||
| component: LayoutComponent, | |||
| canActivate: [authGuard], | |||
| children: [ | |||
| { | |||
| path: 'homepage', | |||
| @@ -17,6 +28,7 @@ const routes: Routes = [ | |||
| }, | |||
| ], | |||
| }, | |||
| { path: '', redirectTo: '/homepage', pathMatch: 'full' }, | |||
| ]; | |||
| @NgModule({ | |||
| @@ -1,19 +1,32 @@ | |||
| import { Component, OnInit } from '@angular/core'; | |||
| import { Component, OnDestroy, OnInit } from '@angular/core'; | |||
| import { MqttClientService } from './shared/services/mqtt-client.service'; | |||
| import { takeUntil } from 'rxjs/operators'; | |||
| import { filter, Subject } from 'rxjs'; | |||
| import { TOPIC_GETTING, TOPIC_SETTING } from './app.constants'; | |||
| import { MatIconRegistry } from '@angular/material/icon'; | |||
| import { DomSanitizer } from '@angular/platform-browser'; | |||
| import * as AOS from 'aos'; | |||
| @Component({ | |||
| selector: 'app-root', | |||
| templateUrl: './app.component.html', | |||
| styleUrls: ['./app.component.scss'], | |||
| }) | |||
| export class AppComponent implements OnInit { | |||
| export class AppComponent implements OnInit, OnDestroy { | |||
| title = 'Iot-web-ui'; | |||
| private unsubscribeAll = new Subject(); | |||
| isConnected = false; | |||
| constructor(private mqtt$: MqttClientService) {} | |||
| constructor( | |||
| private mqtt$: MqttClientService, | |||
| private matIcon: MatIconRegistry, | |||
| private domSanitizer: DomSanitizer, | |||
| ) { | |||
| this.matIcon.addSvgIconSet( | |||
| this.domSanitizer.bypassSecurityTrustResourceUrl( | |||
| 'assets/images/svg/sprite/sprite.svg', | |||
| ), | |||
| ); | |||
| } | |||
| ngOnInit() { | |||
| this.mqtt$.createConnection(); | |||
| @@ -45,5 +58,11 @@ export class AppComponent implements OnInit { | |||
| } | |||
| }); | |||
| }); | |||
| AOS.init({ once: true, offset: -10000 }); | |||
| } | |||
| ngOnDestroy() { | |||
| this.unsubscribeAll.next(''); | |||
| this.unsubscribeAll.complete(); | |||
| } | |||
| } | |||
| @@ -8,6 +8,9 @@ import { SharedModule } from './shared/shared.module'; | |||
| import { HttpClientModule } from '@angular/common/http'; | |||
| import { ToastrModule } from 'ngx-toastr'; | |||
| import { IMqttServiceOptions, MqttModule } from 'ngx-mqtt'; | |||
| import { NgxWebstorageModule } from 'ngx-webstorage'; | |||
| import { MatIconModule } from '@angular/material/icon'; | |||
| export const connection: IMqttServiceOptions = { | |||
| hostname: 'test.mosquitto.org', | |||
| port: 1883, | |||
| @@ -34,6 +37,8 @@ export const connection: IMqttServiceOptions = { | |||
| autoDismiss: true, | |||
| }), // ToastrModule added | |||
| MqttModule.forRoot(connection), | |||
| NgxWebstorageModule.forRoot(), | |||
| MatIconModule, | |||
| ], | |||
| providers: [], | |||
| bootstrap: [AppComponent], | |||
| @@ -0,0 +1,15 @@ | |||
| import { CanActivateFn, Router } from '@angular/router'; | |||
| import { inject } from '@angular/core'; | |||
| import { LocalStorageService } from 'ngx-webstorage'; | |||
| import { RouteUtils } from '../../shared/utils/route.utils'; | |||
| export const authGuard: CanActivateFn = () => { | |||
| const localStorage = inject(LocalStorageService); | |||
| const token = localStorage.retrieve('token'); | |||
| if (token) { | |||
| return true; | |||
| } | |||
| const router = inject(Router); | |||
| router.navigate(['/', RouteUtils.Session.LOGIN]).then(); | |||
| return false; | |||
| }; | |||
| @@ -0,0 +1,14 @@ | |||
| import { CanActivateFn, Router } from '@angular/router'; | |||
| import { inject } from '@angular/core'; | |||
| import { LocalStorageService } from 'ngx-webstorage'; | |||
| export const unAuthGuard: CanActivateFn = () => { | |||
| const localStorage = inject(LocalStorageService); | |||
| const token = localStorage.retrieve('token'); | |||
| if (!token) { | |||
| return true; | |||
| } | |||
| const router = inject(Router); | |||
| router.navigate(['/homepage']).then(); | |||
| return false; | |||
| }; | |||
| @@ -24,7 +24,7 @@ | |||
| [ngClass]="room.status" | |||
| > | |||
| <span class="time-checkin" *ngIf="room.checkInTime"> | |||
| {{ room.checkInTime }} {{clock}} | |||
| {{ room.checkInTime }} {{ clock }} | |||
| </span> | |||
| {{ room?.roomNumber }} | |||
| </div> | |||
| @@ -1,6 +1,6 @@ | |||
| import {Component, OnInit} from '@angular/core'; | |||
| import { Component, OnInit } from '@angular/core'; | |||
| import { hotelData } from '../data/fake-data'; | |||
| import {timer} from "rxjs"; | |||
| import { timer } from 'rxjs'; | |||
| @Component({ | |||
| selector: 'app-hotel-management', | |||
| @@ -9,7 +9,7 @@ import {timer} from "rxjs"; | |||
| }) | |||
| export class HotelManagementComponent implements OnInit { | |||
| data = hotelData.reverse(); | |||
| public clock:any = null; | |||
| public clock: any = null; | |||
| constructor() {} | |||
| ngOnInit() { | |||
| @@ -20,15 +20,15 @@ export class HotelManagementComponent implements OnInit { | |||
| time() { | |||
| let date = new Date(); | |||
| let second:number | string = date.getSeconds(); | |||
| let minute:number | string = date.getMinutes(); | |||
| let hour:number | string = date.getHours(); | |||
| let second: number | string = date.getSeconds(); | |||
| let minute: number | string = date.getMinutes(); | |||
| let hour: number | string = date.getHours(); | |||
| if (second < 10) { | |||
| second = '0' + second | |||
| second = '0' + second; | |||
| } | |||
| if (minute < 0) { | |||
| minute = '0' + minute; | |||
| } | |||
| this.clock = hour + ":" + minute + ":" + second; | |||
| this.clock = hour + ':' + minute + ':' + second; | |||
| } | |||
| } | |||
| @@ -77,7 +77,7 @@ export class SecurityAtmDetailsComponent implements OnInit, OnDestroy { | |||
| this.status2 = message.status2 == '1'; // 0 not, 1 ready, 2 error, 3 bypass | |||
| this.state3 = message.state3 == '1' ? 'ON' : 'OFF'; | |||
| this.state4 = message.state4 == '1' ? 'ON' : 'OFF'; | |||
| this.state5 = true;//message.state5 == '1'; // alarm 9h && 6h // 1 ON, 0 OFF | |||
| this.state5 = true; //message.state5 == '1'; // alarm 9h && 6h // 1 ON, 0 OFF | |||
| this.state6 = message.state6 == '1'; // ? 'ON' : 'OFF'; | |||
| this.switchArm = message.state5 == '1'; // alarm 9h && 6h // 1 ON, 0 OFF | |||
| this.isReady = message.ready == '1'; | |||
| @@ -0,0 +1,94 @@ | |||
| <div class="flex h-full bg-container items-center"> | |||
| <div class="w-[28.75rem] rounded-md p-5 mx-auto bg-orange-50/75"> | |||
| <div class="flex items-center mb-6"> | |||
| <img | |||
| data-aos="fade-up" | |||
| class="h-10" | |||
| src="assets/images/logo.png" | |||
| alt="logo-with-text" | |||
| /> | |||
| <div> | |||
| <h1 | |||
| data-aos="fade-up" | |||
| data-aos-delay="50" | |||
| class="text-24px font-medium mb-2" | |||
| > | |||
| Login to your account | |||
| </h1> | |||
| <p data-aos="fade-up" data-aos-delay="100" class="opacity-60"> | |||
| Enter your username and password to log in. | |||
| </p> | |||
| </div> | |||
| </div> | |||
| <form [formGroup]="form" (ngSubmit)="submit()" class="flex flex-col gap-5"> | |||
| <div data-aos="fade-up" data-aos-delay="150"> | |||
| <mat-label class="block">User name</mat-label> | |||
| <mat-form-field | |||
| class="w-full" | |||
| appearance="outline" | |||
| floatLabel="always" | |||
| subscriptSizing="dynamic" | |||
| > | |||
| <input | |||
| formControlName="username" | |||
| matInput | |||
| placeholder="Username" | |||
| autocomplete="username" | |||
| /> | |||
| </mat-form-field> | |||
| <ng-container | |||
| *ngIf=" | |||
| form.get('username')?.hasError('required') && | |||
| form.get('username')?.touched | |||
| " | |||
| > | |||
| <mat-error>This is a required field.</mat-error> | |||
| </ng-container> | |||
| </div> | |||
| <div data-aos="fade-up" data-aos-delay="200"> | |||
| <mat-label class="block">Password</mat-label> | |||
| <mat-form-field | |||
| class="w-full" | |||
| appearance="outline" | |||
| floatLabel="always" | |||
| subscriptSizing="dynamic" | |||
| > | |||
| <input | |||
| formControlName="password" | |||
| matInput | |||
| placeholder="Password" | |||
| [type]="isShow ? 'text' : 'password'" | |||
| autocomplete="current-password" | |||
| /> | |||
| <mat-icon | |||
| [matTooltip]="isShow ? 'Hide password' : 'Show password'" | |||
| class="cursor-pointer text-gray-500" | |||
| matSuffix | |||
| [svgIcon]="isShow ? 'outline--eye' : 'outline--eye-slash'" | |||
| (click)="isShow = !isShow" | |||
| ></mat-icon> | |||
| </mat-form-field> | |||
| <ng-container | |||
| *ngIf=" | |||
| form.get('password')?.hasError('required') && | |||
| form.get('password')?.touched | |||
| " | |||
| > | |||
| <mat-error>This is a required field.</mat-error> | |||
| </ng-container> | |||
| </div> | |||
| <mat-error *ngIf="loginError" class="text-center" | |||
| >Login failed. Please check your username and password.</mat-error | |||
| > | |||
| <button | |||
| data-aos="fade-up" | |||
| data-aos-delay="250" | |||
| mat-flat-button | |||
| class="w-full !bg-[#ff7723] !text-white py-2" | |||
| [disabled]="isLoading" | |||
| > | |||
| Login | |||
| </button> | |||
| </form> | |||
| </div> | |||
| </div> | |||
| @@ -0,0 +1,5 @@ | |||
| .bg-container { | |||
| background-image: url("/assets/images/login-bg.png"); | |||
| background-repeat: no-repeat; | |||
| background-size: cover; | |||
| } | |||
| @@ -0,0 +1,21 @@ | |||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | |||
| import { SessionComponent } from './session.component'; | |||
| describe('SessionComponent', () => { | |||
| let component: SessionComponent; | |||
| let fixture: ComponentFixture<SessionComponent>; | |||
| beforeEach(() => { | |||
| TestBed.configureTestingModule({ | |||
| declarations: [SessionComponent], | |||
| }); | |||
| fixture = TestBed.createComponent(SessionComponent); | |||
| component = fixture.componentInstance; | |||
| fixture.detectChanges(); | |||
| }); | |||
| it('should create', () => { | |||
| expect(component).toBeTruthy(); | |||
| }); | |||
| }); | |||
| @@ -0,0 +1,72 @@ | |||
| import { Component } from '@angular/core'; | |||
| import { | |||
| FormControl, | |||
| FormGroup, | |||
| ReactiveFormsModule, | |||
| Validators, | |||
| } from '@angular/forms'; | |||
| import { LocalStorageService, SessionStorageService } from 'ngx-webstorage'; | |||
| import { Router } from '@angular/router'; | |||
| import { map, timer } from 'rxjs'; | |||
| import { MatInputModule } from '@angular/material/input'; | |||
| import { MatButtonModule } from '@angular/material/button'; | |||
| import { MatIconModule } from '@angular/material/icon'; | |||
| import { MatTooltipModule } from '@angular/material/tooltip'; | |||
| import { NgIf } from '@angular/common'; | |||
| @Component({ | |||
| selector: 'app-session', | |||
| standalone: true, | |||
| templateUrl: './session.component.html', | |||
| styleUrls: ['./session.component.scss'], | |||
| imports: [ | |||
| ReactiveFormsModule, | |||
| MatInputModule, | |||
| MatButtonModule, | |||
| MatIconModule, | |||
| MatTooltipModule, | |||
| NgIf, | |||
| ], | |||
| }) | |||
| export class SessionComponent { | |||
| readonly token = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFkbWluIiwiaWF0IjoxNTE2MjM5MDIyfQ.1Jy-gGjEJVq6me2f6YMguG5Pgjtmgkw1E7Qucttkbbs`; | |||
| form: FormGroup = new FormGroup({ | |||
| username: new FormControl<string>('', [Validators.required]), | |||
| password: new FormControl<string>('', [Validators.required]), | |||
| }); | |||
| isLoading = false; | |||
| isShow = false; | |||
| loginError: boolean = false; | |||
| constructor( | |||
| private localStorageService: LocalStorageService, | |||
| private router: Router, | |||
| ) {} | |||
| submit(): void { | |||
| if (this.form.invalid) { | |||
| this.form.markAllAsTouched(); | |||
| return; | |||
| } | |||
| this.isLoading = true; | |||
| this.loginError = false; | |||
| timer(200) | |||
| .pipe( | |||
| map(() => { | |||
| const { username, password } = this.form.value; | |||
| return username === 'admin' && password === 'Admin@123'; | |||
| }), | |||
| ) | |||
| .subscribe((isSuccess) => { | |||
| if (isSuccess) { | |||
| this.localStorageService.store('token', this.token); | |||
| this.router.navigate(['/homepage']).then(); | |||
| } else { | |||
| this.loginError = true; // Nếu sai, hiển thị lỗi | |||
| } | |||
| this.isLoading = false; | |||
| }); | |||
| } | |||
| } | |||
| @@ -1,11 +1,39 @@ | |||
| <div class="layout-container"> | |||
| <div class="header mat-elevation-z1"> | |||
| <div class="header mat-elevation-z1 flex justify-between"> | |||
| <div class="flex gap-2 items-center"> | |||
| <img src="../../../../../../assets/images/logo.png" /> | |||
| <div> | |||
| <button mat-button routerLink="/homepage" routerLinkActive="active"> | |||
| <button | |||
| class="w-[5rem]" | |||
| mat-button | |||
| routerLink="/homepage" | |||
| routerLinkActive="active" | |||
| > | |||
| Home | |||
| </button> | |||
| </div> | |||
| <div | |||
| data-aos="fade-left" | |||
| data-aos-delay="250" | |||
| class="inline-flex justify-end items-center gap-2" | |||
| > | |||
| <button | |||
| [matMenuTriggerFor]="menu" | |||
| mat-mini-fab | |||
| class="rounded-full inline-flex justify-center items-center cursor-pointer !bg-orange-50" | |||
| > | |||
| <mat-icon svgIcon="bold--user" class="size-4"></mat-icon> | |||
| </button> | |||
| <mat-menu #menu="matMenu" class="mt-2"> | |||
| <!-- <button mat-menu-item>--> | |||
| <!-- <mat-icon svgIcon="outline--lock" class="size-6"></mat-icon>--> | |||
| <!-- <span>Change password</span>--> | |||
| <!-- </button>--> | |||
| <button mat-menu-item (click)="logout()"> | |||
| <mat-icon svgIcon="outline--logout" class="size-6"></mat-icon> | |||
| <span>Logout</span> | |||
| </button> | |||
| </mat-menu> | |||
| </div> | |||
| </div> | |||
| <div style="flex: 1"> | |||
| <router-outlet></router-outlet> | |||
| @@ -4,7 +4,7 @@ | |||
| justify-content: space-between; | |||
| height: 100%; | |||
| .header { | |||
| height: 3rem; | |||
| height: 3.5rem; | |||
| padding: 0.25rem 2rem; | |||
| display: flex; | |||
| flex-direction: row; | |||
| @@ -13,9 +13,6 @@ | |||
| width: 7rem; | |||
| height: 3rem; | |||
| } | |||
| button { | |||
| width: 5rem; | |||
| } | |||
| .active { | |||
| color: #ff7723 !important; | |||
| } | |||
| @@ -1,8 +1,21 @@ | |||
| import { Component } from '@angular/core'; | |||
| import { LocalStorageService } from 'ngx-webstorage'; | |||
| import { Router } from '@angular/router'; | |||
| import { RouteUtils } from '../../utils/route.utils'; | |||
| @Component({ | |||
| selector: 'app-layout', | |||
| templateUrl: './layout.component.html', | |||
| styleUrls: ['./layout.component.scss'], | |||
| }) | |||
| export class LayoutComponent {} | |||
| export class LayoutComponent { | |||
| constructor( | |||
| private localStorage: LocalStorageService, | |||
| private router: Router, | |||
| ) {} | |||
| logout() { | |||
| this.localStorage.clear('token'); | |||
| this.router.navigate(['/', RouteUtils.Session.LOGIN]); | |||
| } | |||
| } | |||
| @@ -0,0 +1,5 @@ | |||
| export class RouteUtils { | |||
| public static Session = { | |||
| LOGIN: 'login', | |||
| }; | |||
| } | |||
| @@ -0,0 +1 @@ | |||
| <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none"><path d="M12 12a5 5 0 1 0 0-10 5 5 0 0 0 0 10ZM12 14.5c-5.01 0-9.09 3.36-9.09 7.5 0 .28.22.5.5.5h17.18c.28 0 .5-.22.5-.5 0-4.14-4.08-7.5-9.09-7.5Z" fill="#FF8A65"></path></svg> | |||
| @@ -0,0 +1,11 @@ | |||
| <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none"> | |||
| <path | |||
| d="M9.472 15.282c-.19 0-.38-.07-.53-.22a4.305 4.305 0 0 1-1.27-3.06c0-2.39 1.94-4.33 4.33-4.33 1.15 0 2.24.45 3.06 1.27a.75.75 0 0 1 0 1.06l-5.06 5.06c-.15.15-.34.22-.53.22Zm2.53-6.11a2.834 2.834 0 0 0-2.46 4.23l3.86-3.86c-.42-.24-.9-.37-1.4-.37Z" | |||
| fill="currentColor"></path> | |||
| <path | |||
| d="M5.6 18.51c-.17 0-.35-.06-.49-.18-1.07-.91-2.03-2.03-2.85-3.33-1.06-1.65-1.06-4.34 0-6C4.7 5.18 8.25 2.98 12 2.98c2.2 0 4.37.76 6.27 2.19a.75.75 0 0 1-.9 1.2c-1.64-1.24-3.5-1.89-5.37-1.89-3.23 0-6.32 1.94-8.48 5.33-.75 1.17-.75 3.21 0 4.38s1.61 2.18 2.56 3c.31.27.35.74.08 1.06-.14.17-.35.26-.56.26ZM11.999 21.02c-1.33 0-2.63-.27-3.88-.8a.75.75 0 0 1-.4-.98c.16-.38.6-.56.98-.4 1.06.45 2.17.68 3.29.68 3.23 0 6.32-1.94 8.48-5.33.75-1.17.75-3.21 0-4.38-.31-.49-.65-.96-1.01-1.4a.76.76 0 0 1 .11-1.06.75.75 0 0 1 1.06.11c.39.48.77 1 1.11 1.54 1.06 1.65 1.06 4.34 0 6-2.44 3.82-5.99 6.02-9.74 6.02Z" | |||
| fill="currentColor"></path> | |||
| <path | |||
| d="M12.691 16.268c-.35 0-.67-.25-.74-.61-.08-.41.19-.8.6-.87 1.1-.2 2.02-1.12 2.22-2.22.08-.41.47-.67.88-.6.41.08.68.47.6.88-.32 1.73-1.7 3.1-3.42 3.42-.05-.01-.09 0-.14 0ZM2.001 22.749c-.19 0-.38-.07-.53-.22a.755.755 0 0 1 0-1.06l7.47-7.47c.29-.29.77-.29 1.06 0 .29.29.29.77 0 1.06l-7.47 7.47c-.15.15-.34.22-.53.22ZM14.529 10.221c-.19 0-.38-.07-.53-.22a.754.754 0 0 1 0-1.06l7.47-7.47c.29-.29.77-.29 1.06 0 .29.29.29.77 0 1.06l-7.47 7.47c-.15.15-.34.22-.53.22Z" | |||
| fill="currentColor"></path> | |||
| </svg> | |||
| @@ -0,0 +1,8 @@ | |||
| <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none"> | |||
| <path | |||
| d="M12.002 16.332c-2.39 0-4.33-1.94-4.33-4.33s1.94-4.33 4.33-4.33 4.33 1.94 4.33 4.33-1.94 4.33-4.33 4.33Zm0-7.16c-1.56 0-2.83 1.27-2.83 2.83s1.27 2.83 2.83 2.83 2.83-1.27 2.83-2.83-1.27-2.83-2.83-2.83Z" | |||
| fill="currentColor"></path> | |||
| <path | |||
| d="M11.998 21.02c-3.76 0-7.31-2.2-9.75-6.02-1.06-1.65-1.06-4.34 0-6 2.45-3.82 6-6.02 9.75-6.02s7.3 2.2 9.74 6.02c1.06 1.65 1.06 4.34 0 6-2.44 3.82-5.99 6.02-9.74 6.02Zm0-16.54c-3.23 0-6.32 1.94-8.48 5.33-.75 1.17-.75 3.21 0 4.38 2.16 3.39 5.25 5.33 8.48 5.33 3.23 0 6.32-1.94 8.48-5.33.75-1.17.75-3.21 0-4.38-2.16-3.39-5.25-5.33-8.48-5.33Z" | |||
| fill="currentColor"></path> | |||
| </svg> | |||
| @@ -0,0 +1,4 @@ | |||
| <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none"> | |||
| <path stroke="#FF8A65" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.5" | |||
| d="M17.44 14.62L20 12.06 17.44 9.5M9.76 12.06h10.17M11.76 20c-4.42 0-8-3-8-8s3.58-8 8-8"></path> | |||
| </svg> | |||
| @@ -0,0 +1 @@ | |||
| <?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><symbol viewBox="0 0 24 24" fill="none" id="bold--user" xmlns="http://www.w3.org/2000/svg"><path d="M12 12a5 5 0 1 0 0-10 5 5 0 0 0 0 10Zm0 2.5c-5.01 0-9.09 3.36-9.09 7.5 0 .28.22.5.5.5h17.18c.28 0 .5-.22.5-.5 0-4.14-4.08-7.5-9.09-7.5Z" fill="#FF8A65"/></symbol><symbol viewBox="0 0 24 24" fill="none" id="outline--eye" xmlns="http://www.w3.org/2000/svg"><path d="M12.002 16.332c-2.39 0-4.33-1.94-4.33-4.33s1.94-4.33 4.33-4.33 4.33 1.94 4.33 4.33-1.94 4.33-4.33 4.33Zm0-7.16c-1.56 0-2.83 1.27-2.83 2.83s1.27 2.83 2.83 2.83 2.83-1.27 2.83-2.83-1.27-2.83-2.83-2.83Z" fill="currentColor"/><path d="M11.998 21.02c-3.76 0-7.31-2.2-9.75-6.02-1.06-1.65-1.06-4.34 0-6 2.45-3.82 6-6.02 9.75-6.02s7.3 2.2 9.74 6.02c1.06 1.65 1.06 4.34 0 6-2.44 3.82-5.99 6.02-9.74 6.02Zm0-16.54c-3.23 0-6.32 1.94-8.48 5.33-.75 1.17-.75 3.21 0 4.38 2.16 3.39 5.25 5.33 8.48 5.33 3.23 0 6.32-1.94 8.48-5.33.75-1.17.75-3.21 0-4.38-2.16-3.39-5.25-5.33-8.48-5.33Z" fill="currentColor"/></symbol><symbol viewBox="0 0 24 24" fill="none" id="outline--eye-slash" xmlns="http://www.w3.org/2000/svg"><path d="M9.472 15.282c-.19 0-.38-.07-.53-.22a4.305 4.305 0 0 1-1.27-3.06c0-2.39 1.94-4.33 4.33-4.33 1.15 0 2.24.45 3.06 1.27a.75.75 0 0 1 0 1.06l-5.06 5.06c-.15.15-.34.22-.53.22Zm2.53-6.11a2.834 2.834 0 0 0-2.46 4.23l3.86-3.86c-.42-.24-.9-.37-1.4-.37Z" fill="currentColor"/><path d="M5.6 18.51c-.17 0-.35-.06-.49-.18-1.07-.91-2.03-2.03-2.85-3.33-1.06-1.65-1.06-4.34 0-6C4.7 5.18 8.25 2.98 12 2.98c2.2 0 4.37.76 6.27 2.19a.75.75 0 0 1-.9 1.2c-1.64-1.24-3.5-1.89-5.37-1.89-3.23 0-6.32 1.94-8.48 5.33-.75 1.17-.75 3.21 0 4.38s1.61 2.18 2.56 3c.31.27.35.74.08 1.06-.14.17-.35.26-.56.26Zm6.399 2.51c-1.33 0-2.63-.27-3.88-.8a.75.75 0 0 1-.4-.98c.16-.38.6-.56.98-.4 1.06.45 2.17.68 3.29.68 3.23 0 6.32-1.94 8.48-5.33.75-1.17.75-3.21 0-4.38-.31-.49-.65-.96-1.01-1.4a.76.76 0 0 1 .11-1.06.75.75 0 0 1 1.06.11c.39.48.77 1 1.11 1.54 1.06 1.65 1.06 4.34 0 6-2.44 3.82-5.99 6.02-9.74 6.02Z" fill="currentColor"/><path d="M12.691 16.268c-.35 0-.67-.25-.74-.61-.08-.41.19-.8.6-.87 1.1-.2 2.02-1.12 2.22-2.22.08-.41.47-.67.88-.6.41.08.68.47.6.88-.32 1.73-1.7 3.1-3.42 3.42-.05-.01-.09 0-.14 0Zm-10.69 6.481c-.19 0-.38-.07-.53-.22a.755.755 0 0 1 0-1.06l7.47-7.47c.29-.29.77-.29 1.06 0 .29.29.29.77 0 1.06l-7.47 7.47c-.15.15-.34.22-.53.22Zm12.528-12.528c-.19 0-.38-.07-.53-.22a.754.754 0 0 1 0-1.06l7.47-7.47c.29-.29.77-.29 1.06 0 .29.29.29.77 0 1.06l-7.47 7.47c-.15.15-.34.22-.53.22Z" fill="currentColor"/></symbol><symbol viewBox="0 0 24 24" fill="none" id="outline--logout" xmlns="http://www.w3.org/2000/svg"><path stroke="#FF8A65" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.5" d="M17.44 14.62 20 12.06 17.44 9.5m-7.68 2.56h10.17M11.76 20c-4.42 0-8-3-8-8s3.58-8 8-8"/></symbol></svg> | |||
| @@ -1,5 +1,9 @@ | |||
| /* You can add global styles to this file, and also import other style files */ | |||
| @import "assets/style/scss/common"; | |||
| @tailwind base; | |||
| @tailwind components; | |||
| @tailwind utilities; | |||
| html, | |||
| body { | |||
| height: 100%; | |||
| @@ -0,0 +1,8 @@ | |||
| /** @type {import('tailwindcss').Config} */ | |||
| module.exports = { | |||
| content: ["./src/**/*.{html,ts}"], | |||
| theme: { | |||
| extend: {}, | |||
| }, | |||
| plugins: [], | |||
| }; | |||