created auth system
This commit is contained in:
135
src/app/services/location.service.ts
Normal file
135
src/app/services/location.service.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import { Injectable, signal, computed } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Region, GeoIpResponse } from '../models/location.model';
|
||||
import { environment } from '../../environments/environment';
|
||||
|
||||
const STORAGE_KEY = 'selected_region';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class LocationService {
|
||||
private regionSignal = signal<Region | null>(null);
|
||||
private regionsSignal = signal<Region[]>([]);
|
||||
private loadingSignal = signal(false);
|
||||
private detectedSignal = signal(false);
|
||||
|
||||
/** Current selected region (null = global / all regions) */
|
||||
readonly region = this.regionSignal.asReadonly();
|
||||
/** All available regions */
|
||||
readonly regions = this.regionsSignal.asReadonly();
|
||||
/** Whether geo-detection is in progress */
|
||||
readonly detecting = this.loadingSignal.asReadonly();
|
||||
/** Whether region was auto-detected */
|
||||
readonly autoDetected = this.detectedSignal.asReadonly();
|
||||
|
||||
/** Computed region id for API calls — empty string means global */
|
||||
readonly regionId = computed(() => this.regionSignal()?.id ?? '');
|
||||
|
||||
private readonly apiUrl = environment.apiUrl;
|
||||
|
||||
constructor(private http: HttpClient) {
|
||||
this.loadRegions();
|
||||
this.restoreFromStorage();
|
||||
}
|
||||
|
||||
/** Fetch available regions from backend */
|
||||
loadRegions(): void {
|
||||
this.http.get<Region[]>(`${this.apiUrl}/regions`).subscribe({
|
||||
next: (regions) => {
|
||||
this.regionsSignal.set(regions);
|
||||
// If we have a stored region, validate it still exists
|
||||
const stored = this.regionSignal();
|
||||
if (stored && !regions.find(r => r.id === stored.id)) {
|
||||
this.clearRegion();
|
||||
}
|
||||
},
|
||||
error: () => {
|
||||
// Fallback: hardcoded popular regions
|
||||
this.regionsSignal.set(this.getFallbackRegions());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Set region by user choice */
|
||||
setRegion(region: Region): void {
|
||||
this.regionSignal.set(region);
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(region));
|
||||
}
|
||||
|
||||
/** Clear region (go global) */
|
||||
clearRegion(): void {
|
||||
this.regionSignal.set(null);
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
}
|
||||
|
||||
/** Auto-detect user location via IP geolocation */
|
||||
detectLocation(): void {
|
||||
if (this.detectedSignal()) return; // already tried
|
||||
this.loadingSignal.set(true);
|
||||
|
||||
// Using free ip-api.com — no key required, 45 req/min
|
||||
this.http.get<GeoIpResponse>('http://ip-api.com/json/?fields=city,country,countryCode,region,timezone,lat,lon')
|
||||
.subscribe({
|
||||
next: (geo) => {
|
||||
this.detectedSignal.set(true);
|
||||
this.loadingSignal.set(false);
|
||||
|
||||
// Only auto-set if user hasn't manually chosen a region
|
||||
if (!this.regionSignal()) {
|
||||
const matchedRegion = this.findRegionByGeo(geo);
|
||||
if (matchedRegion) {
|
||||
this.setRegion(matchedRegion);
|
||||
}
|
||||
}
|
||||
},
|
||||
error: () => {
|
||||
this.detectedSignal.set(true);
|
||||
this.loadingSignal.set(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Try to match detected geo data to an available region */
|
||||
private findRegionByGeo(geo: GeoIpResponse): Region | null {
|
||||
const regions = this.regionsSignal();
|
||||
if (!regions.length) return null;
|
||||
|
||||
// Exact city match
|
||||
const cityMatch = regions.find(r =>
|
||||
r.city.toLowerCase() === geo.city?.toLowerCase()
|
||||
);
|
||||
if (cityMatch) return cityMatch;
|
||||
|
||||
// Country match — pick the first region for that country
|
||||
const countryMatch = regions.find(r =>
|
||||
r.countryCode.toLowerCase() === geo.countryCode?.toLowerCase()
|
||||
);
|
||||
return countryMatch || null;
|
||||
}
|
||||
|
||||
/** Restore previously selected region from storage */
|
||||
private restoreFromStorage(): void {
|
||||
try {
|
||||
const stored = localStorage.getItem(STORAGE_KEY);
|
||||
if (stored) {
|
||||
const region: Region = JSON.parse(stored);
|
||||
this.regionSignal.set(region);
|
||||
}
|
||||
} catch {
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
/** Fallback regions if backend /regions endpoint is unavailable */
|
||||
private getFallbackRegions(): Region[] {
|
||||
return [
|
||||
{ id: 'moscow', city: 'Москва', country: 'Россия', countryCode: 'RU', timezone: 'Europe/Moscow' },
|
||||
{ id: 'spb', city: 'Санкт-Петербург', country: 'Россия', countryCode: 'RU', timezone: 'Europe/Moscow' },
|
||||
{ id: 'yerevan', city: 'Ереван', country: 'Армения', countryCode: 'AM', timezone: 'Asia/Yerevan' },
|
||||
{ id: 'minsk', city: 'Минск', country: 'Беларусь', countryCode: 'BY', timezone: 'Europe/Minsk' },
|
||||
{ id: 'almaty', city: 'Алматы', country: 'Казахстан', countryCode: 'KZ', timezone: 'Asia/Almaty' },
|
||||
{ id: 'tbilisi', city: 'Тбилиси', country: 'Грузия', countryCode: 'GE', timezone: 'Asia/Tbilisi' },
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user