created auth system

This commit is contained in:
sdarbinyan
2026-02-28 17:18:24 +04:00
parent 86d11364f0
commit 6689acbe57
28 changed files with 1254 additions and 674 deletions

View File

@@ -0,0 +1,54 @@
<div class="region-selector">
<button class="region-trigger" (click)="toggleDropdown()" [class.active]="dropdownOpen()">
<svg class="pin-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>
<circle cx="12" cy="10" r="3"></circle>
</svg>
<span class="region-name">
@if (detecting()) {
<span class="detecting">...</span>
} @else if (region()) {
{{ region()!.city }}
} @else {
{{ 'location.allRegions' | translate }}
}
</span>
<svg class="chevron" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"
[class.rotated]="dropdownOpen()">
<path d="M6 9l6 6 6-6"></path>
</svg>
</button>
@if (dropdownOpen()) {
<div class="region-dropdown">
<div class="dropdown-header">
<span>{{ 'location.chooseRegion' | translate }}</span>
@if (!detecting()) {
<button class="detect-btn" (click)="detectLocation()" title="{{ 'location.detectAuto' | translate }}">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="3"></circle>
<path d="M12 2v4M12 18v4M2 12h4M18 12h4"></path>
</svg>
</button>
}
</div>
<div class="region-list">
<button class="region-option" [class.selected]="!region()" (click)="selectGlobal()">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"></circle>
<path d="M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path>
</svg>
<span>{{ 'location.allRegions' | translate }}</span>
</button>
@for (r of regions(); track r.id) {
<button class="region-option" [class.selected]="region()?.id === r.id" (click)="selectRegion(r)">
<span class="region-city">{{ r.city }}</span>
<span class="region-country">{{ r.country }}</span>
</button>
}
</div>
</div>
}
</div>

View File

@@ -0,0 +1,180 @@
.region-selector {
position: relative;
}
.region-trigger {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 10px;
border: 1px solid var(--border-color, #e0e0e0);
border-radius: 8px;
background: transparent;
cursor: pointer;
font-size: 13px;
color: var(--text-primary, #333);
transition: all 0.2s ease;
white-space: nowrap;
&:hover, &.active {
border-color: var(--accent-color, #497671);
background: var(--bg-hover, rgba(73, 118, 113, 0.05));
}
.pin-icon {
flex-shrink: 0;
color: var(--accent-color, #497671);
}
.region-name {
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
.detecting {
animation: pulse 1s ease infinite;
}
}
.chevron {
flex-shrink: 0;
transition: transform 0.2s ease;
&.rotated {
transform: rotate(180deg);
}
}
}
.region-dropdown {
position: absolute;
top: calc(100% + 4px);
left: 0;
min-width: 220px;
background: var(--bg-card, #fff);
border: 1px solid var(--border-color, #e0e0e0);
border-radius: 12px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
z-index: 1000;
overflow: hidden;
animation: slideDown 0.15s ease;
}
.dropdown-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 14px;
border-bottom: 1px solid var(--border-color, #e0e0e0);
font-size: 12px;
font-weight: 600;
color: var(--text-secondary, #666);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.detect-btn {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border: none;
border-radius: 6px;
background: var(--bg-hover, rgba(73, 118, 113, 0.08));
color: var(--accent-color, #497671);
cursor: pointer;
transition: all 0.2s ease;
&:hover {
background: var(--accent-color, #497671);
color: #fff;
}
}
.region-list {
max-height: 280px;
overflow-y: auto;
padding: 4px;
}
.region-option {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
padding: 10px 12px;
border: none;
border-radius: 8px;
background: transparent;
cursor: pointer;
font-size: 14px;
color: var(--text-primary, #333);
text-align: left;
transition: background 0.15s ease;
&:hover {
background: var(--bg-hover, rgba(73, 118, 113, 0.06));
}
&.selected {
background: var(--accent-color, #497671);
color: #fff;
.region-country {
color: rgba(255, 255, 255, 0.7);
}
}
.region-city {
flex: 1;
}
.region-country {
font-size: 12px;
color: var(--text-secondary, #999);
}
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-4px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
// Mobile adjustments
@media (max-width: 768px) {
.region-trigger {
padding: 5px 8px;
font-size: 12px;
.region-name {
max-width: 80px;
}
}
.region-dropdown {
position: fixed;
top: auto;
bottom: 0;
left: 0;
right: 0;
min-width: 100%;
border-radius: 16px 16px 0 0;
max-height: 60vh;
.region-list {
max-height: 50vh;
}
}
}

View File

@@ -0,0 +1,47 @@
import { Component, ChangeDetectionStrategy, inject, signal, HostListener } from '@angular/core';
import { LocationService } from '../../services/location.service';
import { Region } from '../../models/location.model';
import { TranslatePipe } from '../../i18n/translate.pipe';
@Component({
selector: 'app-region-selector',
imports: [TranslatePipe],
templateUrl: './region-selector.component.html',
styleUrls: ['./region-selector.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class RegionSelectorComponent {
private locationService = inject(LocationService);
region = this.locationService.region;
regions = this.locationService.regions;
detecting = this.locationService.detecting;
dropdownOpen = signal(false);
toggleDropdown(): void {
this.dropdownOpen.update(v => !v);
}
selectRegion(region: Region): void {
this.locationService.setRegion(region);
this.dropdownOpen.set(false);
}
selectGlobal(): void {
this.locationService.clearRegion();
this.dropdownOpen.set(false);
}
detectLocation(): void {
this.locationService.detectLocation();
}
@HostListener('document:click', ['$event'])
onDocumentClick(event: MouseEvent): void {
const target = event.target as HTMLElement;
if (!target.closest('app-region-selector')) {
this.dropdownOpen.set(false);
}
}
}