diff --git a/apps/api/src/auth/auth.service.ts b/apps/api/src/auth/auth.service.ts index 2c8bbba..574b148 100644 --- a/apps/api/src/auth/auth.service.ts +++ b/apps/api/src/auth/auth.service.ts @@ -185,7 +185,7 @@ export class AuthService { } else { // 3. Create new if neither found this.logger.log(`Creating new tenant for Firebase user: ${email} (${uid})`); - const isAdmin = email === 'hamzaaleghwairyeen@gmail.com' || email === 'hamzadoctor@gmail.com'; + const isAdmin = email === 'hamzaaleghwairyeen@gmail.com' || email === 'hamzadoctor@gmail.com' || email === 'hamzaayedpython@gmail.com'; tenant = await this.tenantRepository.save({ firebaseUid: uid, email, @@ -197,7 +197,7 @@ export class AuthService { } } else { // Auto-upgrade admins if they exist but are on lower plan - const isAdmin = email === 'hamzaaleghwairyeen@gmail.com' || email === 'hamzadoctor@gmail.com'; + const isAdmin = email === 'hamzaaleghwairyeen@gmail.com' || email === 'hamzadoctor@gmail.com' || email === 'hamzaayedpython@gmail.com'; if (isAdmin && tenant.plan !== TenantPlan.ENTERPRISE) { tenant.plan = TenantPlan.ENTERPRISE; await this.tenantRepository.save(tenant); diff --git a/apps/api/src/auth/entities/tenant.entity.ts b/apps/api/src/auth/entities/tenant.entity.ts index d0177f0..676fa85 100644 --- a/apps/api/src/auth/entities/tenant.entity.ts +++ b/apps/api/src/auth/entities/tenant.entity.ts @@ -3,6 +3,7 @@ import { ApiKey } from './api-key.entity'; export enum TenantPlan { FREE = 'FREE', + STARTER = 'STARTER', PRO = 'PRO', ENTERPRISE = 'ENTERPRISE', } diff --git a/apps/api/src/auth/tenant.controller.ts b/apps/api/src/auth/tenant.controller.ts index 33aca32..26a85a2 100644 --- a/apps/api/src/auth/tenant.controller.ts +++ b/apps/api/src/auth/tenant.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Get, Post, Body, Param, UseGuards, Req } from '@nestjs/common'; +import { Controller, Get, Post, Body, Param, UseGuards, Req, ForbiddenException } from '@nestjs/common'; import { AuthService } from './auth.service'; import { CreateKeyDto } from './dto/management/create-key.dto'; import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger'; @@ -17,6 +17,10 @@ export class TenantController { @Req() req: any, @Body() dto: CreateKeyDto ) { + if (req.user && req.user.email_verified === false) { + throw new ForbiddenException('You must verify your email address before creating an API key.'); + } + const tenantId = req.tenant.id; return this.authService.createApiKey( tenantId, diff --git a/apps/api/src/billing/billing.controller.ts b/apps/api/src/billing/billing.controller.ts index d97df55..683bbe1 100644 --- a/apps/api/src/billing/billing.controller.ts +++ b/apps/api/src/billing/billing.controller.ts @@ -32,7 +32,18 @@ export class BillingController { async checkout(@Req() req: any, @Body() body: { plan: string; provider: PaymentProvider }) { const tenantId = req.tenant.id; const plan = body.plan; - const amount = plan === 'PRO' ? 40 : 0; // Price logic + + // Mapping prices to plans + const pricing = { + 'STARTER': 29, + 'PRO': 89, + }; + + const amount = pricing[plan] || 0; + + if (amount === 0 && plan !== 'FREE') { + throw new BadRequestException('Invalid plan or price not configured'); + } if (body.provider === PaymentProvider.PAYMOB) { const { paymentKey, orderId } = await this.paymobProvider.createPaymentKey(tenantId, amount, plan); @@ -84,7 +95,7 @@ export class BillingController { } // Redirect back to dashboard with status - const targetUrl = `https://map-dashbord.intaleqapp.com/dashboard.html#billing?payment_status=${query.success === 'true' ? 'success' : 'failed'}&id=${query.id}`; + const targetUrl = `https://map-dashboard.intaleqapp.com/dashboard.html#billing?payment_status=${query.success === 'true' ? 'success' : 'failed'}&id=${query.id}`; return ` diff --git a/apps/api/src/billing/billing.service.ts b/apps/api/src/billing/billing.service.ts index 240b55d..a9f40de 100644 --- a/apps/api/src/billing/billing.service.ts +++ b/apps/api/src/billing/billing.service.ts @@ -40,7 +40,7 @@ export class BillingService { sub = await this.subscriptionRepository.save({ tenantId, plan: 'FREE', - monthlyRequestLimit: 8000, + monthlyRequestLimit: 5000, status: SubscriptionStatus.ACTIVE, }); } @@ -115,9 +115,10 @@ export class BillingService { // Update Subscription const limits = { - [TenantPlan.FREE]: 8000, - [TenantPlan.PRO]: 50000, - [TenantPlan.ENTERPRISE]: 1000000, + [TenantPlan.FREE]: 5000, + [TenantPlan.STARTER]: 25000, + [TenantPlan.PRO]: 100000, + [TenantPlan.ENTERPRISE]: 500000, }; const sub = await this.getSubscription(tenantId); diff --git a/apps/api/src/common/guards/admin.guard.ts b/apps/api/src/common/guards/admin.guard.ts new file mode 100644 index 0000000..8992a18 --- /dev/null +++ b/apps/api/src/common/guards/admin.guard.ts @@ -0,0 +1,20 @@ +import { CanActivate, ExecutionContext, Injectable, UnauthorizedException, ForbiddenException } from '@nestjs/common'; +import { TenantPlan } from '../../auth/entities/tenant.entity'; + +@Injectable() +export class AdminGuard implements CanActivate { + canActivate(context: ExecutionContext): boolean { + const request = context.switchToHttp().getRequest(); + const tenant = request['tenant']; // Populated by ApiKeyGuard + + if (!tenant) { + throw new UnauthorizedException('Tenant not found in request'); + } + + if (tenant.plan !== TenantPlan.ENTERPRISE) { + throw new ForbiddenException('Admin access required. Your tenant plan must be ENTERPRISE.'); + } + + return true; + } +} diff --git a/apps/api/src/geocoding/geocoding.controller.ts b/apps/api/src/geocoding/geocoding.controller.ts index 86c01c7..6eef3dc 100644 --- a/apps/api/src/geocoding/geocoding.controller.ts +++ b/apps/api/src/geocoding/geocoding.controller.ts @@ -6,6 +6,7 @@ import { JordanResearchService } from './jordan-research.service'; import { AdministrativeLinkingService } from './administrative-linking.service'; import { MapRefinementService } from './map-refinement.service'; import { ApiKeyGuard } from '../common/guards/api-key.guard'; +import { AdminGuard } from '../common/guards/admin.guard'; import { TenantThrottlerGuard } from '../common/guards/rate-limiter.guard'; import { SearchQueryDto } from './dto/search-query.dto'; import { ReverseGeocodeDto } from './dto/reverse-geocode.dto'; @@ -47,6 +48,7 @@ export class GeocodingController { } @Delete('places') + @UseGuards(AdminGuard) @ApiOperation({ summary: 'Delete a place by name or ID' }) @ApiQuery({ name: 'name', required: false }) @ApiQuery({ name: 'id', required: false, type: Number }) @@ -66,12 +68,14 @@ export class GeocodingController { } @Post('upsert-place') + @UseGuards(AdminGuard) @ApiOperation({ summary: 'Add or Update a location (Automated Scraper)' }) async upsertPlace(@Body() placeData: any) { return this.geocodingService.upsertPlace(placeData); } @Post('upsert-batch') + @UseGuards(AdminGuard) @ApiOperation({ summary: 'Add or Update multiple locations in bulk' }) async upsertBatch(@Body() body: { places: any[] }) { return this.geocodingService.upsertBatch(body.places); @@ -90,6 +94,7 @@ export class GeocodingController { } @Post('import-boundaries') + @UseGuards(AdminGuard) @ApiOperation({ summary: 'Import administrative boundaries from a local GeoJSON file on the server' }) @ApiQuery({ name: 'country', required: true }) @ApiQuery({ name: 'filePath', required: true }) @@ -107,6 +112,7 @@ export class GeocodingController { } @Post('admin/sync-neighborhoods') + @UseGuards(AdminGuard) @ApiOperation({ summary: 'Sync neighborhood points from OSM for a bbox' }) @ApiQuery({ name: 'bbox', required: false }) @ApiQuery({ name: 'country', required: false, enum: ['jordan', 'syria', 'egypt'] }) @@ -115,6 +121,7 @@ export class GeocodingController { } @Post('admin/generate-voronoi') + @UseGuards(AdminGuard) @ApiOperation({ summary: 'Generate Voronoi polygons for neighborhoods' }) @ApiQuery({ name: 'country', required: false, enum: ['jordan', 'syria', 'egypt'] }) async generateVoronoi(@Query('country') country?: string) { @@ -122,6 +129,7 @@ export class GeocodingController { } @Post('admin/link-places') + @UseGuards(AdminGuard) @ApiOperation({ summary: 'Link places to administrative hierarchy' }) @ApiQuery({ name: 'country', required: true, enum: ['jordan', 'syria', 'egypt'] }) async linkPlaces(@Query('country') country: 'jordan' | 'syria' | 'egypt') { diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index 196fd98..f05d0b5 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -10,6 +10,15 @@ async function bootstrap() { logger: ['error', 'warn', 'log', 'debug', 'verbose'], }); + // Trust proxy for correct rate limiting behind Nginx / Cloudflare + const httpAdapter = app.getHttpAdapter(); + if (httpAdapter && httpAdapter.getInstance && typeof httpAdapter.getInstance().set === 'function') { + httpAdapter.getInstance().set('trust proxy', 1); + } + + // Apply standard HTTP security headers + app.use(helmet()); + // 1. Modern Security Headers & Permissive CORS for Production Dashboard app.enableCors({ origin: true, // Reflect request origin diff --git a/apps/api/src/usage/usage.controller.ts b/apps/api/src/usage/usage.controller.ts index ea8043d..dda69e6 100644 --- a/apps/api/src/usage/usage.controller.ts +++ b/apps/api/src/usage/usage.controller.ts @@ -18,12 +18,13 @@ export class UsageController { // Limits const limits = { - FREE: 8000, - PRO: 50000, - ENTERPRISE: 1000000, + FREE: 5000, + STARTER: 25000, + PRO: 100000, + ENTERPRISE: 500000, }; - const limit = limits[tenant.plan] || 8000; + const limit = limits[tenant.plan] || 5000; return { ...summary, diff --git a/apps/api/src/usage/usage.interceptor.ts b/apps/api/src/usage/usage.interceptor.ts index 31b9c51..6812e02 100644 --- a/apps/api/src/usage/usage.interceptor.ts +++ b/apps/api/src/usage/usage.interceptor.ts @@ -13,13 +13,15 @@ import { TenantPlan } from '../auth/entities/tenant.entity'; // Quota Limits per Plan const QUOTA_LIMITS: Record = { - [TenantPlan.FREE]: 8000, - [TenantPlan.PRO]: 50000, - [TenantPlan.ENTERPRISE]: 1000000, + [TenantPlan.FREE]: 5000, + [TenantPlan.STARTER]: 25000, + [TenantPlan.PRO]: 100000, + [TenantPlan.ENTERPRISE]: 500000, }; const RATE_LIMITS: Record = { - [TenantPlan.FREE]: 10, + [TenantPlan.FREE]: 5, + [TenantPlan.STARTER]: 100, [TenantPlan.PRO]: 500, [TenantPlan.ENTERPRISE]: 5000, }; diff --git a/apps/dashboard/dashboard.html b/apps/dashboard/dashboard.html index 28d9abb..a3d8fd2 100644 --- a/apps/dashboard/dashboard.html +++ b/apps/dashboard/dashboard.html @@ -107,8 +107,8 @@ -

Intaleq Maps

-

The premium developer platform for mapping services in Jordan & Syria.

+

Intaleq Maps

+

The premium developer platform for mapping services in Jordan & Syria.

-

Enterprise Ready · Secure Access

+

Enterprise Ready · Secure Access

@@ -167,8 +167,8 @@
-

Beta Access

-

You're currently on the free sandbox tier.

+

Beta Access

+

You're currently on the free sandbox tier.

Upgrade Plan
@@ -190,7 +190,7 @@
-

Loading...

+

Loading...

developer@intaleq.com

@@ -204,8 +204,8 @@
-

Welcome back...

-

Everything you need to build with premium Jordan Map Platform API

+

Welcome back...

+

Everything you need to build with premium Jordan Map Platform API

@@ -235,7 +235,7 @@
- Live + Live

Active Keys

...
@@ -262,7 +262,7 @@
-

... requests left

+

... requests left

@@ -272,10 +272,10 @@

Request Traffic

-

Live traffic across all API endpoints

+

Live traffic across all API endpoints

- Full Analytics + Full Analytics
@@ -285,20 +285,20 @@
-

Quick Start

-

Get started with our lightweight SDK in seconds.

+

Quick Start

+

Get started with our lightweight SDK in seconds.

-

# Install with npm

+

# Install with npm

npm install @intaleq/maps-gl

- Try Maps Playground + Try Maps Playground
@@ -308,12 +308,12 @@
-

Your API Keys

-

Manage keys for your applications

+

Your API Keys

+

Manage keys for your applications

@@ -321,23 +321,21 @@ - - - - - + + + + + - -
NameAPI KeyStatusRestrictionsActionsNameAPI KeyStatusRestrictionsActions
-

Fetching your secure keys...

+

Fetching your secure keys...

@@ -347,22 +345,22 @@
-

Maps Playground

-

Test your API keys and visualize vector tiles in real-time

+

Maps Playground

+

Test your API keys and visualize vector tiles in real-time

-

Configuration

+

Configuration

- +
- +
@@ -379,7 +377,7 @@
-
@@ -391,7 +389,7 @@

Analytics

-

Deep insights into your API performance

+

Deep insights into your API performance

@@ -426,7 +424,7 @@

Place Audit

-

Review and approve user-suggested map locations

+

Review and approve user-suggested map locations

@@ -434,21 +432,19 @@ - - - - + + + + - -
Location NameAutomated MatchSpatial ContextActionsLocation NameAutomated MatchSpatial ContextActions
-

No pending location suggestions.

+

No pending location suggestions.

@@ -459,7 +455,7 @@

Subscription & Billing

-

Manage your plan, payment methods, and invoice history.

+

Manage your plan, payment methods, and invoice history.

@@ -469,8 +465,8 @@
-

Current Plan: FREE

-

Your monthly usage resets on the 1st of each month.

+

Current Plan: FREE

+

Your monthly usage resets on the 1st of each month.

@@ -481,50 +477,79 @@
-
+
- Starter -

$0 /mo

+ Sandbox +

$0 /mo

  • - 8,000 requests /mo + 5,000 requests /mo +
  • +
  • + 5 requests / min
  • - Standard Map Tiles + Standard Map Tiles
  • - Basic Geocoding + No Routing API
- + +
+ + +
+
+ Starter +

$29 /mo

+
+
    +
  • + 25,000 requests /mo +
  • +
  • + 100 requests / min +
  • +
  • + 3D Building Data +
  • +
  • + Basic Geocoding +
  • +
+
-
Popular
+
Best Value
- Professional -

$40 /mo

+ Professional +

$89 /mo

  • - 50,000 requests /mo + 100,000 requests /mo +
  • +
  • + 500 requests / min
  • - 3D Building Extrusion + 3D Building Extrusion
  • - Advanced Routing API + Advanced Routing API
  • - Priority Support + Priority Support
- + @@ -534,24 +559,27 @@
- Enterprise -

Custom

+ Enterprise +

$299 /mo

  • - Unlimited Requests + 500,000 requests /mo +
  • +
  • + 5,000 requests / min
  • - Custom Data Layers + Custom Data Layers
  • - SLA Guarantees + SLA Guarantees
  • - Dedicated Architect + Dedicated Architect
- +
@@ -565,10 +593,10 @@ - - - - + + + + @@ -577,13 +605,15 @@
DateAmountProviderStatusDateAmountProviderStatus
+
+
-

Guides

+

Guides

Getting Started @@ -613,20 +643,20 @@
-

Create API Key

-

Set up a new access point for your application.

+

Create API Key

+

Set up a new access point for your application.

- +
- -
@@ -638,11 +668,11 @@ + - diff --git a/apps/dashboard/js/app.js b/apps/dashboard/js/app.js index d0c0869..b866b32 100644 --- a/apps/dashboard/js/app.js +++ b/apps/dashboard/js/app.js @@ -19,6 +19,9 @@ const app = { // Check for payment success/fail status earlier app.checkPaymentStatus(); + // Trigger initial routing based on URL hash + app.handleRouting(); + if (window.lucide) lucide.createIcons(); } catch (e) { console.error('Core UI Init failed', e); diff --git a/apps/dashboard/js/billing.js b/apps/dashboard/js/billing.js index acabfd0..5de06c1 100644 --- a/apps/dashboard/js/billing.js +++ b/apps/dashboard/js/billing.js @@ -47,7 +47,7 @@ const billing = { document.querySelectorAll('.current-plan-name').forEach(el => el.textContent = sub.plan); // 2. Render Plan Cards logic - const plans = ['FREE', 'PRO', 'ENTERPRISE']; + const plans = ['FREE', 'STARTER', 'PRO', 'ENTERPRISE']; plans.forEach(p => { const card = document.getElementById(`plan-card-${p.toLowerCase()}`); if (card) { diff --git a/apps/dashboard/js/docs.js b/apps/dashboard/js/docs.js index 4006211..95268c0 100644 --- a/apps/dashboard/js/docs.js +++ b/apps/dashboard/js/docs.js @@ -1,23 +1,28 @@ /** * Documentation Engine for Intaleq Dashboard + * Comprehensive API Reference (EN/AR) + * Updated with Premium Visuals & High-Performance Examples */ const docs = { init: () => { - console.log('📚 Initializing Documentation...'); + console.log('📚 Intaleq Documentation Module Init'); docs.bindEvents(); - docs.renderSection('getting-started'); + + // Initial render based on existing active links or default + const activeLink = document.querySelector('.docs-nav-link.active'); + const section = activeLink ? activeLink.getAttribute('data-section') : 'getting-started'; + docs.renderSection(section); }, bindEvents: () => { - // Handle side-nav clicks document.querySelectorAll('.docs-nav-link').forEach(link => { link.addEventListener('click', (e) => { e.preventDefault(); const section = e.currentTarget.getAttribute('data-section'); docs.renderSection(section); - // Active state + // Active state management document.querySelectorAll('.docs-nav-link').forEach(l => l.classList.remove('active', 'bg-blue-500/10', 'text-blue-400')); e.currentTarget.classList.add('active', 'bg-blue-500/10', 'text-blue-400'); }); @@ -28,68 +33,163 @@ const docs = { const container = document.getElementById('docs-content'); if (!container) return; - // Content repository + const lang = i18n.currentLang || 'en'; + const isAr = lang === 'ar'; + const content = { 'getting-started': ` -
-
-

Getting Started

-

Welcome to the Intaleq Map Platform. Our APIs allow you to integrate high-quality vector maps, geocoding, and routing into your web and mobile applications with ease.

+
+
+
+
+

${isAr ? 'انطلق في ثوانٍ' : 'Launch in Seconds'}

+

+ ${isAr ? 'مرحباً بك في مستقبل الخرائط في المنطقة. توفر لك منصة "انطلاق" واجهات برمجية ذكية، خرائط Vector فائقة الدقة، ومباني ثلاثية الأبعاد متكاملة.' : 'Welcome to the future of regional mapping. Intaleq provides high-fidelity vector tiles, intelligent geocoding, and native 3D building support for Jordan & Syria.'} +

+
-
-
-

- - 1. Get an API Key -

-

Go to the Credentials page and create your first API key.

+
+
+
01
+

${isAr ? 'مفتاح الوصول' : 'Access Key'}

+

${isAr ? 'قم بإنشاء مفتاح API من لوحة التحكم لتفعيل طلباتك.' : 'Generate your secure API key from the dashboard to authenticate requests.'}

-
-

- - 2. Install SDK -

-

Use our MapLibre wrappers for JavaScript or Flutter.

+
+
02
+

${isAr ? 'تكامل الخريطة' : 'Map Integration'}

+

${isAr ? 'اختر النمط (Obsidian أو Light) وادمج الخريطة في تطبيقك.' : 'Select a theme and integrate the vector tiles using our GL styles.'}

+
+
+
03
+

${isAr ? 'بيانات ذكية' : 'Smart Data'}

+

${isAr ? 'استخدم خدمات البحث والتوجيه لإضافة ذكاء مكاني لتطبيقك.' : 'Leverage Geocoding and Routing APIs for advanced spatial intelligence.'}

-
-

Base URL

-
- https://map-dashbord.intaleqapp.com/api +
+
+
+

${isAr ? 'بيانات الوصول والمصادقة' : 'Domain & Authentication'}

+
+ +
+
+ Production URL +
+ https://map-saas.intaleq.com/api +
+
+

${isAr ? 'طريقة المصادقة' : 'Auth Method'}

+

${isAr ? 'يتم إرسال المفتاح عبر الـ HTTP Header التالي:' : 'Pass your API key in the following HTTP header:'}

+ x-api-key +
+
+

${isAr ? 'نطاق الوصول' : 'Allowed Origins'}

+

${isAr ? 'تأكد من إضافة النطاق الخاص بك في إعدادات المفتاح.' : 'Ensure your request origin is listed in the key restrictions.'}

+
+
+
+
+ +
+
+
+

${isAr ? 'حدود الاستخدام (RPM)' : 'Rate Limiting (RPM)'}

+
+ +
+
+

Free

+

5 RPM

+
+
+

Starter

+

100 RPM

+
+
+

Pro

+

500 RPM

+
+
+

Enterprise

+

5000 RPM

+
`, 'tiles-api': ` -
-
-

Vector Tiles API

-

Render high-performance vector maps from our global database.

-
+
+
+
+

${isAr ? 'خرائط الـ Vector' : 'Vector Tiles API'}

+

${isAr ? 'خرائط تفاعلية فائقة السرعة تدعم العرض ثلاثي الأبعاد والتحكم الكامل في الخصائص.' : 'High-performance interactive maps with native 3D buildings and custom GL styles.'}

+
+
+ Active + V1.2 +
+
-
-
-
-
- GET - /maps/style.json -
+
+
+
+
GET
+ /v1/maps/style.json
-
-

Returns the MapLibre-compatible style configuration. Use the theme parameter to switch between 'light' and 'obsidian'.

- -
Code Example
-
-
-// Initialize MapLibre with Intaleq Style
+                            
+                        
+
+
+
+
+
${isAr ? 'المعاملات المدعومة' : 'Query Parameters'}
+
+
+
+ theme +

${isAr ? 'نمط الخريطة (light أو obsidian)' : 'Map visual theme (light | obsidian)'}

+
+ Optional +
+
+
+ 3d +

${isAr ? 'تفعيل/إلغاء المباني ثلاثية الأبعاد' : 'Enable/Disable 3D buildings'}

+
+ Boolean +
+
+
+ +
+ + ${isAr ? 'ملاحظة: يتم استهلاك رصيد الخرائط بناءً على عدد الـ (Tile Requests) التي يتم طلبها أثناء التنقل في الخريطة.' : 'Note: Quota is consumed per tile request. High-density 3D areas may consume more resources.'} +
+
+ +
+
+
+ + JavaScript (MapLibre) + +
+
 const map = new maplibregl.Map({
-    container: 'map',
-    style: 'https://map-dashbord.intaleqapp.com/api/maps/style.json?theme=obsidian',
-    center: [35.9106, 31.9539], // Amman
-    zoom: 12
+    container: 'map-id',
+    style: 'https://map-saas.intaleq.com/api/v1/maps/style.json?theme=obsidian',
+    transformRequest: (url) => {
+        return {
+            url: url,
+            headers: { 'x-api-key': 'YOUR_KEY_HERE' }
+        }
+    }
 });
+
@@ -97,50 +197,163 @@ const map = new maplibregl.Map({
`, 'geocoding-api': ` -
-
-

Geocoding API

-

Convert addresses to coordinates (Forward) or coordinates to addresses (Reverse).

-
+
+
+

${isAr ? 'البحث المكاني (Geocoding)' : 'Geocoding API'}

+

${isAr ? 'حوّل العناوين إلى إحداثيات أو العكس بدقة غير مسبوقة في الأردن وسوريا.' : 'Transform addresses into coordinates or reverse resolve locations with extreme accuracy in the Levant region.'}

+
-
-
- GET - /geocoding/search +
+ +
+
+
+
SEARCH
+ /v1/geocoding/search +
+
+
+
+
+ + + + + + + + + + + + + + + + + +
${isAr ? 'البارامتر' : 'Key'}${isAr ? 'الوصف' : 'Description'}
q${isAr ? 'نص البحث (مثلاً: "عمان، الجبيهة")' : 'Query string (e.g., "Amman, Jordan")'}
limit${isAr ? 'عدد النتائج (الافتراضي: 5)' : 'Max results limit'}
+ +
+
+ + cURL Fast Access +
+ +curl "https://map-saas.intaleq.com/api/v1/geocoding/search?q=Amman" \\
+     -H "x-api-key: YOUR_SECURE_KEY" +
+
+
+ +
+
+ + Response Structure + + application/json +
+
+[
+  {
+    "name": "Amman",
+    "name_ar": "عمان",
+    "lat": 31.9539,
+    "lng": 35.9106,
+    "district": "Capital",
+    "country": "Jordan",
+    "type": "city",
+    "boundingbox": [31.81, 32.07, 35.72, 36.14]
+  }
+]
+
+
+
-
- - - - - - - - - - - - - - - - - - - - -
ParameterTypeDescription
qstringSearch query (address, place, coordinates)
limitnumberMax results (default: 5)
+
+
+ `, + 'routing-api': ` +
+
+
+
+

${isAr ? 'محرك التوجيه (Routing)' : 'Routing Engine'}

+

${isAr ? 'حساب أسرع المسارات مع تحليلات لحظية لحركة المرور في مراكز المدن.' : 'Fast pathfinding with traffic-aware duration metrics for urban environments.'}

+
+
+
+ +
+
+
-
Request
-
curl "https://map-dashbord.intaleqapp.com/api/geocoding/search?q=Amman&limit=1" \\
-     -H "x-api-key: YOUR_API_KEY"
+
+
+
+
ROUTE
+ /v1/routing/route +
+
+
+
+
+
+
${isAr ? 'إحداثيات المسار' : 'Waypoints & Logic'}
+
+
+ start +

"35.91,31.95" (lng,lat)

+
+
+ end +

"35.85,31.82" (lng,lat)

+
+
+
+ +
+
+ profile + car | bike | foot +
+
+ alternatives + Boolean +
+
+
+ +
+
+ + Traffic Analytics JSON + +
+
+{
+  "distance": 8420.5,
+  "duration": 940,
+  "traffic_duration": 1120,
+  "geometry": "encoded_polyline_here",
+  "steps": [
+     { "instruction": "Turn right...", "distance": 200 }
+  ]
+}
+
+
` }; - container.innerHTML = content[id] || '

Documentation section coming soon...

'; - lucide.createIcons(); + container.innerHTML = content[id] || '
Documentation section coming soon...
'; + + // Re-initialize icons + if (window.lucide) lucide.createIcons(); } }; + +// Global initializer +window.docs = docs; diff --git a/apps/dashboard/js/i18n.js b/apps/dashboard/js/i18n.js index 497ff13..d1dd480 100644 --- a/apps/dashboard/js/i18n.js +++ b/apps/dashboard/js/i18n.js @@ -1,5 +1,7 @@ /** - * i18n Translation Engine + * Localization Engine for Intaleq Map SaaS + * Supports Arabic (RTL) and English (LTR) + * Handles both landing page and dashboard */ const i18n = { @@ -7,29 +9,28 @@ const i18n = { translations: { en: { - // Navbar + // Navbar (Landing) 'nav-features': 'Features', 'nav-why': 'Why Us?', 'nav-pricing': 'Pricing', 'nav-launch': 'Launch Dashboard', - 'lang-toggle': 'العربية', - // Hero + // Hero (Landing) 'hero-badge': 'Now with 3D Buildings in Jordan & Syria', 'hero-title': 'The Map API That
Doesn\'t Break The Bank.', 'hero-desc': 'Build premium location-based apps with high-fidelity vector tiles, optimized routing, and 3D buildings. 85% cheaper than Google Maps.', 'hero-cta-start': 'Start Building Free', 'hero-cta-view': 'View Comparison', - // Features + // Features (Landing) 'feat-latency-title': 'Zero Latency', 'feat-latency-desc': 'Our infrastructure is optimized for MENA region, ensuring map tiles load in under 200ms.', 'feat-routing-title': 'Smart Routing', 'feat-routing-desc': 'Enterprise-grade routing engine with support for alternative paths and traffic awareness.', 'feat-geocoding-title': 'Local Geocoding', 'feat-geocoding-desc': 'Highly accurate search for local landmarks and neighborhoods in Jordan & Syria.', - - // Dashboard Sidebar + + // Sidebar (Dashboard) 'side-dashboard': 'Dashboard', 'side-playground': 'Playground', 'side-analytics': 'Analytics', @@ -37,116 +38,251 @@ const i18n = { 'side-audit': 'Place Audit', 'side-docs': 'Documentation', 'side-upgrade': 'Upgrade Plan', + 'side-beta': 'Beta Access', + 'side-free-msg': "You're currently on the sandbox tier.", - // Dashboard General - 'welcome': 'Welcome back', + // Header 'system-status': 'System Operational', - 'quota-card-title': 'Monthly Quota', - 'requests-left': 'requests left', - 'used-label': 'USED', + 'lang-toggle': 'العربية', + 'loading': 'Loading...', - // KPI Labels + // Login + 'login-title': 'Intaleq Maps', + 'login-subtitle': 'The premium developer platform for mapping services in Jordan & Syria.', + 'login-google': 'Continue with Google', + 'login-footer': 'Enterprise Ready · Secure Access', + + // Home / Dashboard + 'welcome': 'Welcome back', + 'tagline': 'Everything you need to build with premium Jordan Map Platform API', 'kpi-total-req': 'Total Requests', 'kpi-success-rate': 'Success Rate', 'kpi-active-keys': 'Active Keys', 'kpi-latency': 'Avg Latency', - + 'quota-card-title': 'Monthly Quota', + 'req-left': 'requests left', + 'live': 'Live', + 'chart-request-volume': 'Request Traffic', + 'chart-subtitle': 'Live traffic across all API endpoints', + 'full-analytics': 'Full Analytics', + 'quick-start': 'Quick Start', + 'quick-start-msg': 'Get started with our lightweight SDK in seconds.', + 'install-npm': '# Install with npm', + 'view-api-ref': 'View API Reference', + 'try-playground': 'Try Maps Playground', + + // Keys Table + 'keys-title': 'Your API Keys', + 'keys-subtitle': 'Manage keys for your applications', + 'keys-create': 'Create New Key', + 'keys-fetching': 'Fetching your secure keys...', + 'table-name': 'Name', + 'table-key': 'API Key', + 'table-status': 'Status', + 'table-restrictions': 'Restrictions', + 'table-actions': 'Actions', + 'table-loc-name': 'Location Name', + 'table-match': 'Automated Match', + 'table-spatial': 'Spatial Context', + 'table-date': 'Date', + 'table-amount': 'Amount', + 'table-provider': 'Provider', + + // Playground + 'playground-subtitle': 'Test your API keys and visualize vector tiles in real-time', + 'pg-config': 'Configuration', + 'pg-active-key': 'Active API Key', + 'pg-style': 'Map Style', + 'pg-search-placeholder': 'Search Amman, Jordan...', + // Analytics - 'chart-request-volume': 'Request Volume (Overall)', - 'chart-latency-breakdown': 'Latency Breakdown (ms)', + 'analytics-subtitle': 'Deep insights into your API performance', 'chart-success-ratio': 'Success Ratio', - 'chart-days': ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], - + 'chart-latency-breakdown': 'Latency Breakdown', + + // Audit + 'audit-subtitle': 'Review and approve user-suggested map locations', + 'audit-empty': 'No pending location suggestions.', + // Billing - 'bill-current-plan': 'Your Current Plan', - 'bill-renewal': 'Renewal', - 'bill-usage-snapshot': 'Usage Snapshot', - 'bill-used': 'used', - 'bill-limit': 'limit', - 'bill-invoices': 'Invoice History', - 'bill-status-active': 'Active', - - // Place Audit - 'audit-title': 'Place Audit & Contributions', - 'audit-desc': 'Review and approve community submitted locations.', - 'audit-no-data': 'No pending contributions found.', + 'billing-subtitle': 'Manage your plan, payment methods, and invoice history.', + 'current-plan': 'Current Plan', + 'billing-reset-msg': 'Your monthly usage resets on the 1st of each month.', + 'active': 'Active', + 'plan-sandbox': 'Sandbox', + 'plan-starter-title': 'Starter', + 'plan-pro-title': 'Professional', + 'plan-ent-title': 'Enterprise', + 'popular': 'Best Value', + 'mo': 'mo', + 'req-mo': 'requests /mo', + 'req-min': 'requests / min', + 'feature-tiles': 'Standard Map Tiles', + 'feature-no-routing': 'No Routing API', + 'feature-3d': '3D Building Data', + 'feature-3d-ext': '3D Building Extrusion', + 'feature-basic-geo': 'Basic Geocoding', + 'feature-routing': 'Advanced Routing API', + 'feature-priority': 'Priority Support', + 'feature-custom': 'Custom Data Layers', + 'feature-sla': 'SLA Guarantees', + 'feature-dedicated': 'Dedicated Architect', + 'plan-current': 'Current Plan', + 'plan-upgrade-starter': 'Upgrade to Starter', + 'pay-card': 'Pay with Card', + 'contact-sales': 'Contact Sales', + 'bill-transaction-history': 'Transaction History', + + // Modals + 'modal-key-title': 'Create API Key', + 'modal-key-subtitle': 'Set up a new access point for your application.', + 'modal-key-label': 'Key Name', + 'cancel': 'Cancel', + 'guides-title': 'Guides' }, ar: { - // Navbar + // Navbar (Landing) 'nav-features': 'المميزات', 'nav-why': 'لماذا نحن؟', 'nav-pricing': 'الأسعار', 'nav-launch': 'لوحة التحكم', - 'lang-toggle': 'English', - - // Hero + + // Hero (Landing) 'hero-badge': 'الآن مع المباني ثلاثية الأبعاد في الأردن وسوريا', 'hero-title': 'واجهة خرائط برمجية
لا ترهق ميزانيتك.', 'hero-desc': 'ابنِ تطبيقات خرائط فاخرة مع خرائط مجهزة، توجيه ذكي، ومباني ثلاثية الأبعاد. أوفر بنسبة 85% من خرائط جوجل.', 'hero-cta-start': 'ابدأ مجاناً', 'hero-cta-view': 'قارن الأسعار', - - // Features + + // Features (Landing) 'feat-latency-title': 'سرعة فائقة', 'feat-latency-desc': 'بنيتنا التحتية محسنة لمنطقة الشرق الأوسط، مما يضمن تحميل الخرائط في أقل من 200 مللي ثانية.', 'feat-routing-title': 'توجيه ذكي', 'feat-routing-desc': 'محرك توجيه من الفئة المؤسسية يدعم المسارات البديلة والوعي بحركة المرور.', 'feat-geocoding-title': 'بحث مكاني محلي', 'feat-geocoding-desc': 'دقة عالية جداً في البحث عن المعالم والأحياء في الأردن وسوريا.', - - // Dashboard Sidebar + + // Sidebar (Dashboard) 'side-dashboard': 'لوحة التحكم', - 'side-playground': 'ساحة الاختبار', + 'side-playground': 'المختبر', 'side-analytics': 'التحليلات', 'side-billing': 'الفواتير', 'side-audit': 'تدقيق الأماكن', 'side-docs': 'التوثيق', 'side-upgrade': 'ترقية الخطة', + 'side-beta': 'وصول تجريبي', + 'side-free-msg': 'أنت حالياً على الخطة المجانية.', - // Dashboard General - 'welcome': 'مرحباً بك مجدداً', + // Header 'system-status': 'النظام يعمل بكفاءة', - 'quota-card-title': 'رصيد الاستهلاك', - 'requests-left': 'طلب متبقي', - 'used-label': 'مستهلك', + 'lang-toggle': 'English', + 'loading': 'جاري التحميل...', - // KPI Labels + // Login + 'login-title': 'انطلاق للخرائط', + 'login-subtitle': 'منصة المطورين الأولى لخدمات الخرائط في الأردن وسوريا.', + 'login-google': 'المتابعة باستخدام جوجل', + 'login-footer': 'جاهز للمؤسسات · وصول آمن', + + // Home / Dashboard + 'welcome': 'مرحباً بك مجدداً', + 'tagline': 'كل ما تحتاجه للبناء باستخدام واجهة خرائط الأردن المتطورة', 'kpi-total-req': 'إجمالي الطلبات', 'kpi-success-rate': 'نسبة النجاح', 'kpi-active-keys': 'المفاتيح النشطة', - 'kpi-latency': 'متوسط سرعة الاستجابة', - + 'kpi-latency': 'متوسط الاستجابة', + 'quota-card-title': 'الرصيد الشهري', + 'req-left': 'طلب متبقي', + 'live': 'مباشر', + 'chart-request-volume': 'نشاط الطلبات', + 'chart-subtitle': 'حركة البيانات المباشرة عبر جميع الواجهات', + 'full-analytics': 'التحليلات الكاملة', + 'quick-start': 'البداية السريعة', + 'quick-start-msg': 'ابدأ الدمج باستخدام مكتبتنا البرمجية في ثوانٍ.', + 'install-npm': '# التثبيت عبر npm', + 'view-api-ref': 'عرض مرجع الـ API', + 'try-playground': 'تجربة المختبر', + + // Keys Table + 'keys-title': 'مفاتيح الـ API الخاصة بك', + 'keys-subtitle': 'إدارة مفاتيح الوصول لتطبيقاتك', + 'keys-create': 'إنشاء مفتاح جديد', + 'keys-fetching': 'جاري جلب مفاتيحك الآمنة...', + 'table-name': 'الاسم', + 'table-key': 'مفتاح الـ API', + 'table-status': 'الحالة', + 'table-restrictions': 'القيود', + 'table-actions': 'الإجراءات', + 'table-loc-name': 'اسم الموقع', + 'table-match': 'المطابقة التلقائية', + 'table-spatial': 'السياق المكاني', + 'table-date': 'التاريخ', + 'table-amount': 'المبلغ', + 'table-provider': 'المزود', + + // Playground + 'playground-subtitle': 'اختبر مفاتيحك وعاين خرائط الـ Vector في الوقت الفعلي', + 'pg-config': 'الإعدادات', + 'pg-active-key': 'مفتاح الـ API النشط', + 'pg-style': 'تنسيق الخريطة', + 'pg-search-placeholder': 'ابحث في عمان، الأردن...', + // Analytics - 'chart-request-volume': 'حجم الطلبات الكلية', - 'chart-latency-breakdown': 'تحليل سرعة الاستجابة (مللي ثانية)', - 'chart-success-ratio': 'نسبة نجاح الطلبات', - 'chart-days': ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], - + 'analytics-subtitle': 'رؤى عميقة حول أداء واجهات البرمجة الخاصة بك', + 'chart-success-ratio': 'نسبة النجاح', + 'chart-latency-breakdown': 'تفاصيل وقت الاستجابة', + + // Audit + 'audit-subtitle': 'مراجعة واعتماد المواقع المقترحة من المستخدمين', + 'audit-empty': 'لا توجد مقترحات مواقع معلقة حالياً.', + // Billing - 'bill-current-plan': 'خطتك الحالية', - 'bill-renewal': 'تاريخ التجديد', - 'bill-usage-snapshot': 'نظرة على الاستهلاك', - 'bill-used': 'مستهلك', - 'bill-limit': 'الحد الأقصى', - 'bill-invoices': 'سجل الفواتير', - 'bill-status-active': 'نشطة', - - // Place Audit - 'audit-title': 'تدقيق الأماكن والمساهمات', - 'audit-desc': 'مراجعة واعتماد المواقع المصافة من قبل المجتمع.', - 'audit-no-data': 'لا توجد مساهمات معلقة حالياً.', + 'billing-subtitle': 'إدارة خطتك، وسائل الدفع، وسجل الفواتير.', + 'current-plan': 'الخطة الحالية', + 'billing-reset-msg': 'يتم إعادة تعيين رصيدك في اليوم الأول من كل شهر.', + 'active': 'نشط', + 'plan-sandbox': 'تجريبية', + 'plan-starter-title': 'مبتدئ', + 'plan-pro-title': 'احترافي', + 'plan-ent-title': 'مؤسسات', + 'popular': 'الأكثر قيمة', + 'mo': 'شهر', + 'req-mo': 'طلب / شهر', + 'req-min': 'طلب / دقيقة', + 'feature-tiles': 'خرائط Vector القياسية', + 'feature-no-routing': 'بدون خدمة المسارات', + 'feature-3d': 'بيانات المباني ثلاثية الأبعاد', + 'feature-3d-ext': 'مباني ثلاثية الأبعاد (Extrusion)', + 'feature-basic-geo': 'تكويد جغرافي أساسي', + 'feature-routing': 'واجهة مسارات متطورة', + 'feature-priority': 'دعم فني ذو أولوية', + 'feature-custom': 'طبقات بيانات مخصصة', + 'feature-sla': 'ضمانات مستوى الخدمة', + 'feature-dedicated': 'مهندس دعم مخصص', + 'plan-current': 'الخطة الحالية', + 'plan-upgrade-starter': 'ترقية إلى "مبتدئ"', + 'pay-card': 'الدفع بالبطاقة', + 'contact-sales': 'تواصل مع المبيعات', + 'bill-transaction-history': 'سجل العمليات', + + // Modals + 'modal-key-title': 'إنشاء مفتاح API', + 'modal-key-subtitle': 'إعداد نقطة وصول جديدة لتطبيقك.', + 'modal-key-label': 'اسم المفتاح', + 'cancel': 'إلغاء', + 'guides-title': 'الأدلة برمجية' } }, init: () => { + console.log(`🌍 i18n Initializing: ${i18n.currentLang}`); i18n.apply(i18n.currentLang); }, toggle: () => { - const nextLang = i18n.currentLang === 'en' ? 'ar' : 'en'; - i18n.currentLang = nextLang; - localStorage.setItem('intaleq_lang', nextLang); - i18n.apply(nextLang); + i18n.currentLang = i18n.currentLang === 'en' ? 'ar' : 'en'; + localStorage.setItem('intaleq_lang', i18n.currentLang); + location.reload(); }, apply: (lang) => { @@ -154,7 +290,7 @@ const i18n = { document.documentElement.dir = rtl ? 'rtl' : 'ltr'; document.documentElement.lang = lang; - // Apply font classes + // Apply font families if (rtl) { document.body.style.fontFamily = "'Cairo', sans-serif"; document.body.classList.add('rtl-mode'); @@ -163,7 +299,7 @@ const i18n = { document.body.classList.remove('rtl-mode'); } - // Mirror Sidebar if in dashboard + // Sidebar mirroring logic const sidebar = document.getElementById('main-sidebar'); if (sidebar) { if (rtl) { @@ -175,7 +311,7 @@ const i18n = { } } - // Translate elements with data-i18n attribute + // Apply text content document.querySelectorAll('[data-i18n]').forEach(el => { const key = el.getAttribute('data-i18n'); const translation = i18n.translations[lang][key]; @@ -188,10 +324,17 @@ const i18n = { } }); - // Trigger icon re-render if lucide is present + // Apply placeholders + document.querySelectorAll('[data-i18n-placeholder]').forEach(el => { + const key = el.getAttribute('data-i18n-placeholder'); + const translation = i18n.translations[lang][key]; + if (translation) el.placeholder = translation; + }); + + // Trigger icon re-render if (window.lucide) lucide.createIcons(); } }; -// Initialize on load +// Auto-init document.addEventListener('DOMContentLoaded', i18n.init); diff --git a/infrastructure/docker/dashboard/nginx.conf b/infrastructure/docker/dashboard/nginx.conf index d23540b..b5111be 100644 --- a/infrastructure/docker/dashboard/nginx.conf +++ b/infrastructure/docker/dashboard/nginx.conf @@ -1,7 +1,15 @@ +# Rate limiting zones +limit_req_zone $binary_remote_addr zone=tile_limit:10m rate=100r/s; +limit_req_zone $binary_remote_addr zone=api_limit:10m rate=30r/s; + server { listen 80; server_name localhost; + # Gzip compression for better performance + gzip on; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; + location / { root /usr/share/nginx/html; index index.html; @@ -10,17 +18,26 @@ server { # Proxy API requests to the api service in docker location /api/ { + limit_req zone=api_limit burst=20 nodelay; proxy_pass http://api:3200/api/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # Proxy Martin Vector Tiles (if accessed via dashboard directly) location /tiles/ { + limit_req zone=tile_limit burst=50 nodelay; proxy_pass http://martin:3000/; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # Cache tiles for performance + add_header Cache-Control "public, max-age=3600"; } error_page 500 502 503 504 /50x.html; diff --git a/packages/flutter-sdk/lib/intaleq_maps.dart b/packages/flutter-sdk/lib/intaleq_maps.dart new file mode 100644 index 0000000..cf30cf5 --- /dev/null +++ b/packages/flutter-sdk/lib/intaleq_maps.dart @@ -0,0 +1,49 @@ +import 'package:maplibre_gl/maplibre_gl.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; + +class IntaleqMapController { + final MapLibreMapController mapController; + final String apiKey; + + IntaleqMapController({ + required this.mapController, + required this.apiKey, + }); + + /// Search for places using Intaleq Geocoding API + Future> search(String query) async { + final response = await http.get( + Uri.parse( + 'https://map-saas.intaleq.com/v1/geocoding/search?q=$query&key=$apiKey'), + ); + if (response.statusCode == 200) { + return json.decode(response.body); + } + throw Exception('Failed to search'); + } + + /// Get route using Intaleq Routing API + Future> getRoute( + LatLng start, + LatLng end, { + String profile = 'car', + }) async { + final response = await http.get( + Uri.parse( + 'https://map-saas.intaleq.com/v1/routing/route?start=${start.longitude},${start.latitude}&end=${end.longitude},${end.latitude}&profile=$profile&key=$apiKey', + ), + ); + if (response.statusCode == 200) { + return json.decode(response.body); + } + throw Exception('Failed to fetch route'); + } +} + +class IntaleqStyles { + static String obsidian(String apiKey) => + 'https://maps.intaleq.com/styles/obsidian/style.json?key=$apiKey'; + static String light(String apiKey) => + 'https://maps.intaleq.com/styles/light/style.json?key=$apiKey'; +} diff --git a/packages/flutter-sdk/pubspec.yaml b/packages/flutter-sdk/pubspec.yaml new file mode 100644 index 0000000..ee9c2f9 --- /dev/null +++ b/packages/flutter-sdk/pubspec.yaml @@ -0,0 +1,22 @@ +name: intaleq_maps +description: Premium Flutter SDK for Intaleq Map Platform (Jordan & Syria). +version: 1.0.0 +homepage: https://intaleq.com + +environment: + sdk: ">=3.0.0 <4.0.0" + flutter: ">=3.0.0" + +dependencies: + flutter: + sdk: flutter + maplibre_gl: ^0.19.0 # Use standard MapLibre plugin + http: ^1.1.0 + +dev_dependencies: + flutter_test: + sdk: flutter + lints: ^2.1.0 + +flutter: + uses-material-design: true diff --git a/packages/js-sdk/package.json b/packages/js-sdk/package.json new file mode 100644 index 0000000..67198e0 --- /dev/null +++ b/packages/js-sdk/package.json @@ -0,0 +1,31 @@ +{ + "name": "@intaleq/maps-gl", + "version": "1.0.0", + "description": "Premium JavaScript SDK for Intaleq Map Platform", + "main": "dist/index.js", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsup src/index.ts --format cjs,esm --dts", + "dev": "tsup src/index.ts --format cjs,esm --watch --dts", + "lint": "eslint src/**" + }, + "keywords": [ + "maps", + "intaleq", + "jordan", + "syria", + "vector-tiles", + "maplibre" + ], + "author": "Intaleq team", + "license": "MIT", + "peerDependencies": { + "maplibre-gl": "^5.1.1" + }, + "devDependencies": { + "maplibre-gl": "^5.1.1", + "tsup": "^8.0.0", + "typescript": "^5.0.0" + } +} diff --git a/packages/js-sdk/src/index.ts b/packages/js-sdk/src/index.ts new file mode 100644 index 0000000..cbe630e --- /dev/null +++ b/packages/js-sdk/src/index.ts @@ -0,0 +1,61 @@ +import maplibregl from 'maplibre-gl'; + +export interface IntaleqMapOptions extends Omit { + container: string | HTMLElement; + apiKey: string; + styleType?: 'obsidian' | 'light' | 'hybrid'; +} + +/** + * Intaleq Maps JS SDK + * Optimized for Jordan & Syria mapping services. + */ +export class IntaleqMap extends maplibregl.Map { + private readonly intaleqApiKey: string; + + constructor(options: IntaleqMapOptions) { + const { apiKey, styleType = 'obsidian', ...mapOptions } = options; + + // Construct the Intaleq style URL + // In production, this points to our tile server with the API key + const styleUrl = `https://maps.intaleq.com/styles/${styleType}/style.json?key=${apiKey}`; + + super({ + ...mapOptions, + style: styleUrl, + hash: mapOptions.hash ?? true, + center: mapOptions.center ?? [35.9239, 31.9522], // Default to Amman + zoom: mapOptions.zoom ?? 12, + transformRequest: (url: string) => { + // Automatically append API key to all tile and asset requests + if (url.includes('intaleq.com')) { + const separator = url.includes('?') ? '&' : '?'; + return { + url: `${url}${separator}key=${apiKey}` + }; + } + return { url }; + } + }); + + this.intaleqApiKey = apiKey; + } + + /** + * Helper to search for places using Intaleq Geocoding API + */ + async search(query: string) { + const response = await fetch(`https://map-saas.intaleq.com/v1/geocoding/search?q=${encodeURIComponent(query)}&key=${this.intaleqApiKey}`); + return response.json(); + } + + /** + * Helper to get routes using Intaleq Routing API + */ + async getRoute(start: [number, number], end: [number, number], profile: 'car' | 'bike' | 'foot' = 'car') { + const response = await fetch(`https://map-saas.intaleq.com/v1/routing/route?start=${start.join(',')}&end=${end.join(',')}&profile=${profile}&key=${this.intaleqApiKey}`); + return response.json(); + } +} + +export { maplibregl };