@progalaxyelabs/ngx-stonescriptphp-client 1.5.1 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,9 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { Injectable, Inject, NgModule, EventEmitter, Output, Input, Component, Optional } from '@angular/core';
3
3
  import { BehaviorSubject } from 'rxjs';
4
- import * as i2$1 from '@angular/common';
4
+ import * as i3 from '@angular/common';
5
5
  import { CommonModule } from '@angular/common';
6
- import * as i2 from '@angular/forms';
6
+ import * as i4 from '@angular/forms';
7
7
  import { FormsModule } from '@angular/forms';
8
8
 
9
9
  class ApiResponse {
@@ -196,6 +196,19 @@ class MyEnvironmentModel {
196
196
  };
197
197
  apiServer = { host: '' };
198
198
  chatServer = { host: '' };
199
+ /**
200
+ * Accounts/Authentication service server configuration
201
+ * Use this when auth is on a different server than the API
202
+ * Replaces the deprecated accountsUrl string with structured config
203
+ * @example { host: 'https://accounts.progalaxyelabs.com' }
204
+ */
205
+ accountsServer;
206
+ /**
207
+ * Files service server configuration
208
+ * Used by FilesService for file upload/download operations
209
+ * @example { host: 'https://files.progalaxyelabs.com/api/' }
210
+ */
211
+ filesServer;
199
212
  /**
200
213
  * Authentication configuration
201
214
  * @default { mode: 'cookie', refreshEndpoint: '/auth/refresh', useCsrf: true }
@@ -208,6 +221,19 @@ class MyEnvironmentModel {
208
221
  csrfTokenCookieName: 'csrf_token',
209
222
  csrfHeaderName: 'X-CSRF-Token'
210
223
  };
224
+ /**
225
+ * Custom OAuth provider configurations.
226
+ * Register additional OAuth providers beyond the built-in ones
227
+ * (google, linkedin, apple, microsoft, github, zoho).
228
+ * @example
229
+ * ```typescript
230
+ * customProviders: {
231
+ * okta: { label: 'Sign in with Okta', cssClass: 'btn-okta', buttonStyle: { borderColor: '#007dc1' } },
232
+ * keycloak: { label: 'Sign in with Keycloak', icon: '🔑' }
233
+ * }
234
+ * ```
235
+ */
236
+ customProviders;
211
237
  /**
212
238
  * Branding configuration for auth components
213
239
  * Allows platforms to customize login/register pages without creating wrappers
@@ -264,7 +290,7 @@ class ApiConnectionService {
264
290
  signinStatus;
265
291
  environment;
266
292
  csrf;
267
- host = ''; // contains trailing slash
293
+ host = ''; // base URL without trailing slash
268
294
  accessToken = '';
269
295
  authConfig;
270
296
  constructor(tokens, signinStatus, environment, csrf) {
@@ -328,7 +354,7 @@ class ApiConnectionService {
328
354
  return new ApiResponse('error');
329
355
  }
330
356
  async get(endpoint, queryParamsObj) {
331
- const url = this.host + endpoint.replace(/^\/+/, '') + this.buildQueryString(queryParamsObj);
357
+ const url = this.host + endpoint + this.buildQueryString(queryParamsObj);
332
358
  const fetchOptions = {
333
359
  mode: 'cors',
334
360
  redirect: 'error'
@@ -336,7 +362,7 @@ class ApiConnectionService {
336
362
  return this.request(url, fetchOptions, null);
337
363
  }
338
364
  async post(pathWithQueryParams, data) {
339
- const url = this.host + pathWithQueryParams.replace(/^\/+/, '');
365
+ const url = this.host + pathWithQueryParams;
340
366
  const fetchOptions = {
341
367
  method: 'POST',
342
368
  mode: 'cors',
@@ -349,7 +375,7 @@ class ApiConnectionService {
349
375
  return this.request(url, fetchOptions, data);
350
376
  }
351
377
  async put(pathWithQueryParams, data) {
352
- const url = this.host + pathWithQueryParams.replace(/^\/+/, '');
378
+ const url = this.host + pathWithQueryParams;
353
379
  const fetchOptions = {
354
380
  method: 'PUT',
355
381
  mode: 'cors',
@@ -362,7 +388,7 @@ class ApiConnectionService {
362
388
  return this.request(url, fetchOptions, data);
363
389
  }
364
390
  async patch(pathWithQueryParams, data) {
365
- const url = this.host + pathWithQueryParams.replace(/^\/+/, '');
391
+ const url = this.host + pathWithQueryParams;
366
392
  const fetchOptions = {
367
393
  method: 'PATCH',
368
394
  mode: 'cors',
@@ -375,7 +401,7 @@ class ApiConnectionService {
375
401
  return this.request(url, fetchOptions, data);
376
402
  }
377
403
  async delete(endpoint, queryParamsObj) {
378
- const url = this.host + endpoint.replace(/^\/+/, '') + this.buildQueryString(queryParamsObj);
404
+ const url = this.host + endpoint + this.buildQueryString(queryParamsObj);
379
405
  const fetchOptions = {
380
406
  method: 'DELETE',
381
407
  mode: 'cors',
@@ -441,7 +467,11 @@ class ApiConnectionService {
441
467
  */
442
468
  async refreshAccessTokenCookieMode() {
443
469
  try {
444
- const refreshTokenUrl = this.host + this.authConfig.refreshEndpoint.replace(/^\/+/, '');
470
+ // Determine auth server: accountsServer.host > accountsUrl > apiServer.host
471
+ const authHost = this.environment.accountsServer?.host
472
+ || this.environment.accountsUrl
473
+ || this.host;
474
+ const refreshTokenUrl = authHost + this.authConfig.refreshEndpoint;
445
475
  const headers = {
446
476
  'Content-Type': 'application/json'
447
477
  };
@@ -497,7 +527,11 @@ class ApiConnectionService {
497
527
  if (!refreshToken) {
498
528
  return false;
499
529
  }
500
- const refreshTokenUrl = this.host + this.authConfig.refreshEndpoint.replace(/^\/+/, '');
530
+ // Determine auth server: accountsServer.host > accountsUrl > apiServer.host
531
+ const authHost = this.environment.accountsServer?.host
532
+ || this.environment.accountsUrl
533
+ || this.host;
534
+ const refreshTokenUrl = authHost + this.authConfig.refreshEndpoint;
501
535
  let refreshTokenResponse = await fetch(refreshTokenUrl, {
502
536
  method: 'POST',
503
537
  mode: 'cors',
@@ -557,7 +591,7 @@ class ApiConnectionService {
557
591
  */
558
592
  async uploadDrawing(formData) {
559
593
  const uploadHost = this.environment.uploadServer?.host || this.host;
560
- const url = uploadHost + 'upload/drawing';
594
+ const url = uploadHost + '/upload/drawing';
561
595
  const fetchOptions = {
562
596
  method: 'POST',
563
597
  mode: 'cors',
@@ -572,7 +606,7 @@ class ApiConnectionService {
572
606
  */
573
607
  async uploadImage(formData) {
574
608
  const uploadHost = this.environment.uploadServer?.host || this.host;
575
- const url = uploadHost + 'upload/image';
609
+ const url = uploadHost + '/upload/image';
576
610
  const fetchOptions = {
577
611
  method: 'POST',
578
612
  mode: 'cors',
@@ -1492,6 +1526,452 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
1492
1526
  }]
1493
1527
  }], ctorParameters: () => [] });
1494
1528
 
1529
+ /**
1530
+ * Service for interacting with the stonescriptphp-files server.
1531
+ * Handles file upload, download, list, and delete operations
1532
+ * with automatic Bearer token injection and 401 refresh handling.
1533
+ */
1534
+ class FilesService {
1535
+ tokens;
1536
+ signinStatus;
1537
+ environment;
1538
+ csrf;
1539
+ host = '';
1540
+ apiHost = '';
1541
+ authConfig;
1542
+ constructor(tokens, signinStatus, environment, csrf) {
1543
+ this.tokens = tokens;
1544
+ this.signinStatus = signinStatus;
1545
+ this.environment = environment;
1546
+ this.csrf = csrf;
1547
+ this.host = environment.filesServer?.host || '';
1548
+ this.apiHost = environment.apiServer.host;
1549
+ this.authConfig = {
1550
+ mode: environment.auth?.mode || 'cookie',
1551
+ refreshEndpoint: environment.auth?.refreshEndpoint,
1552
+ useCsrf: environment.auth?.useCsrf,
1553
+ refreshTokenCookieName: environment.auth?.refreshTokenCookieName || 'refresh_token',
1554
+ csrfTokenCookieName: environment.auth?.csrfTokenCookieName || 'csrf_token',
1555
+ csrfHeaderName: environment.auth?.csrfHeaderName || 'X-CSRF-Token'
1556
+ };
1557
+ if (!this.authConfig.refreshEndpoint) {
1558
+ this.authConfig.refreshEndpoint = this.authConfig.mode === 'cookie'
1559
+ ? '/auth/refresh'
1560
+ : '/user/refresh_access';
1561
+ }
1562
+ if (this.authConfig.useCsrf === undefined) {
1563
+ this.authConfig.useCsrf = this.authConfig.mode === 'cookie';
1564
+ }
1565
+ }
1566
+ /**
1567
+ * Check if the files server is configured.
1568
+ */
1569
+ isConfigured() {
1570
+ return !!this.host;
1571
+ }
1572
+ /**
1573
+ * Upload a file to the files service.
1574
+ * Uses FormData — does NOT set Content-Type header (browser sets multipart boundary).
1575
+ *
1576
+ * @param file The File object to upload
1577
+ * @param entityType Optional entity type for server-side reference linking
1578
+ * @param entityId Optional entity ID for server-side reference linking
1579
+ * @returns Promise resolving to the upload result
1580
+ */
1581
+ async upload(file, entityType, entityId) {
1582
+ const formData = new FormData();
1583
+ formData.append('file', file);
1584
+ if (entityType) {
1585
+ formData.append('entityType', entityType);
1586
+ }
1587
+ if (entityId) {
1588
+ formData.append('entityId', entityId);
1589
+ }
1590
+ const url = this.host + 'upload';
1591
+ const options = {
1592
+ method: 'POST',
1593
+ mode: 'cors',
1594
+ redirect: 'error',
1595
+ body: formData
1596
+ // No Content-Type header — browser sets multipart boundary automatically
1597
+ };
1598
+ const response = await this.requestWithRetry(url, options);
1599
+ return response.file;
1600
+ }
1601
+ /**
1602
+ * Download a file from the files service.
1603
+ * Returns a Blob suitable for URL.createObjectURL().
1604
+ *
1605
+ * @param fileId UUID of the file to download
1606
+ * @returns Promise resolving to the file Blob
1607
+ */
1608
+ async download(fileId) {
1609
+ const url = this.host + 'files/' + fileId;
1610
+ const options = {
1611
+ method: 'GET',
1612
+ mode: 'cors',
1613
+ redirect: 'error'
1614
+ };
1615
+ this.includeAccessToken(options);
1616
+ let response = await fetch(url, options);
1617
+ if (response.status === 401) {
1618
+ const refreshed = await this.refreshAccessToken();
1619
+ if (refreshed) {
1620
+ this.includeAccessToken(options);
1621
+ response = await fetch(url, options);
1622
+ }
1623
+ }
1624
+ if (response.status === 401) {
1625
+ this.signinStatus.signedOut();
1626
+ }
1627
+ if (!response.ok) {
1628
+ throw new Error(`Download failed: ${response.status} ${response.statusText}`);
1629
+ }
1630
+ return response.blob();
1631
+ }
1632
+ /**
1633
+ * List all files for the current user.
1634
+ *
1635
+ * @returns Promise resolving to array of file metadata
1636
+ */
1637
+ async list() {
1638
+ const url = this.host + 'files';
1639
+ const options = {
1640
+ method: 'GET',
1641
+ mode: 'cors',
1642
+ redirect: 'error'
1643
+ };
1644
+ const response = await this.requestWithRetry(url, options);
1645
+ return response.files;
1646
+ }
1647
+ /**
1648
+ * Delete a file from the files service.
1649
+ *
1650
+ * @param fileId UUID of the file to delete
1651
+ * @returns Promise resolving to true on success
1652
+ */
1653
+ async delete(fileId) {
1654
+ const url = this.host + 'files/' + fileId;
1655
+ const options = {
1656
+ method: 'DELETE',
1657
+ mode: 'cors',
1658
+ redirect: 'error'
1659
+ };
1660
+ const response = await this.requestWithRetry(url, options);
1661
+ return response.success;
1662
+ }
1663
+ /**
1664
+ * Make a request with automatic Bearer token injection and 401 retry.
1665
+ */
1666
+ async requestWithRetry(url, options) {
1667
+ this.includeAccessToken(options);
1668
+ let response = await fetch(url, options);
1669
+ if (response.status === 401) {
1670
+ const refreshed = await this.refreshAccessToken();
1671
+ if (refreshed) {
1672
+ this.includeAccessToken(options);
1673
+ response = await fetch(url, options);
1674
+ }
1675
+ }
1676
+ if (response.status === 401) {
1677
+ this.signinStatus.signedOut();
1678
+ }
1679
+ if (!response.ok) {
1680
+ const body = await response.json().catch(() => ({}));
1681
+ throw new Error(body.message || `Request failed: ${response.status}`);
1682
+ }
1683
+ return response.json();
1684
+ }
1685
+ includeAccessToken(options) {
1686
+ const accessToken = this.tokens.getAccessToken();
1687
+ if (!accessToken)
1688
+ return;
1689
+ if (!options.headers) {
1690
+ options.headers = {};
1691
+ }
1692
+ options.headers['Authorization'] = 'Bearer ' + accessToken;
1693
+ }
1694
+ async refreshAccessToken() {
1695
+ if (this.authConfig.mode === 'none') {
1696
+ return false;
1697
+ }
1698
+ if (this.authConfig.mode === 'cookie') {
1699
+ return this.refreshAccessTokenCookieMode();
1700
+ }
1701
+ else {
1702
+ return this.refreshAccessTokenBodyMode();
1703
+ }
1704
+ }
1705
+ async refreshAccessTokenCookieMode() {
1706
+ try {
1707
+ const refreshTokenUrl = this.apiHost + this.authConfig.refreshEndpoint.replace(/^\/+/, '');
1708
+ const headers = {
1709
+ 'Content-Type': 'application/json'
1710
+ };
1711
+ if (this.authConfig.useCsrf) {
1712
+ const csrfToken = this.csrf.getCsrfToken(this.authConfig.csrfTokenCookieName);
1713
+ if (!csrfToken) {
1714
+ return false;
1715
+ }
1716
+ headers[this.authConfig.csrfHeaderName] = csrfToken;
1717
+ }
1718
+ const response = await fetch(refreshTokenUrl, {
1719
+ method: 'POST',
1720
+ mode: 'cors',
1721
+ credentials: 'include',
1722
+ redirect: 'error',
1723
+ headers
1724
+ });
1725
+ if (!response.ok) {
1726
+ this.tokens.clear();
1727
+ return false;
1728
+ }
1729
+ const data = await response.json();
1730
+ if (!data || data.status !== 'ok') {
1731
+ return false;
1732
+ }
1733
+ const newAccessToken = data.data?.access_token || data.access_token;
1734
+ if (!newAccessToken) {
1735
+ return false;
1736
+ }
1737
+ this.tokens.setAccessToken(newAccessToken);
1738
+ return true;
1739
+ }
1740
+ catch {
1741
+ this.tokens.clear();
1742
+ return false;
1743
+ }
1744
+ }
1745
+ async refreshAccessTokenBodyMode() {
1746
+ try {
1747
+ const refreshToken = this.tokens.getRefreshToken();
1748
+ if (!refreshToken) {
1749
+ return false;
1750
+ }
1751
+ const accessToken = this.tokens.getAccessToken();
1752
+ const refreshTokenUrl = this.apiHost + this.authConfig.refreshEndpoint.replace(/^\/+/, '');
1753
+ const response = await fetch(refreshTokenUrl, {
1754
+ method: 'POST',
1755
+ mode: 'cors',
1756
+ redirect: 'error',
1757
+ headers: { 'Content-Type': 'application/json' },
1758
+ body: JSON.stringify({
1759
+ access_token: accessToken,
1760
+ refresh_token: refreshToken
1761
+ })
1762
+ });
1763
+ if (!response.ok) {
1764
+ this.tokens.clear();
1765
+ return false;
1766
+ }
1767
+ const data = await response.json();
1768
+ if (!data) {
1769
+ return false;
1770
+ }
1771
+ const newAccessToken = data.data?.access_token || data.access_token;
1772
+ if (!newAccessToken) {
1773
+ return false;
1774
+ }
1775
+ this.tokens.setTokens(newAccessToken, refreshToken);
1776
+ return true;
1777
+ }
1778
+ catch {
1779
+ this.tokens.clear();
1780
+ return false;
1781
+ }
1782
+ }
1783
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: FilesService, deps: [{ token: TokenService }, { token: SigninStatusService }, { token: MyEnvironmentModel }, { token: CsrfService }], target: i0.ɵɵFactoryTarget.Injectable });
1784
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: FilesService, providedIn: 'root' });
1785
+ }
1786
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: FilesService, decorators: [{
1787
+ type: Injectable,
1788
+ args: [{
1789
+ providedIn: 'root'
1790
+ }]
1791
+ }], ctorParameters: () => [{ type: TokenService }, { type: SigninStatusService }, { type: MyEnvironmentModel }, { type: CsrfService }] });
1792
+
1793
+ /**
1794
+ * Default configurations for built-in OAuth providers.
1795
+ * These serve as fallback values when no custom configuration is registered.
1796
+ */
1797
+ const BUILT_IN_PROVIDERS = {
1798
+ google: {
1799
+ label: 'Google',
1800
+ cssClass: 'btn-google',
1801
+ buttonStyle: { borderColor: '#4285f4' }
1802
+ },
1803
+ linkedin: {
1804
+ label: 'LinkedIn',
1805
+ cssClass: 'btn-linkedin',
1806
+ buttonStyle: { borderColor: '#0077b5' }
1807
+ },
1808
+ apple: {
1809
+ label: 'Apple',
1810
+ cssClass: 'btn-apple',
1811
+ buttonStyle: { borderColor: '#000' }
1812
+ },
1813
+ microsoft: {
1814
+ label: 'Microsoft',
1815
+ cssClass: 'btn-microsoft',
1816
+ buttonStyle: { borderColor: '#00a4ef' }
1817
+ },
1818
+ github: {
1819
+ label: 'GitHub',
1820
+ cssClass: 'btn-github',
1821
+ buttonStyle: { borderColor: '#333' }
1822
+ },
1823
+ zoho: {
1824
+ label: 'Zoho',
1825
+ icon: '🔶',
1826
+ cssClass: 'btn-zoho',
1827
+ buttonStyle: {
1828
+ borderColor: '#d63b32',
1829
+ backgroundColor: '#f0483e',
1830
+ color: '#ffffff'
1831
+ }
1832
+ },
1833
+ emailPassword: {
1834
+ label: 'Email',
1835
+ cssClass: 'btn-email'
1836
+ }
1837
+ };
1838
+ /**
1839
+ * Service for managing OAuth provider configurations.
1840
+ *
1841
+ * Provides a central registry for both built-in and custom OAuth providers.
1842
+ * Custom providers can be registered either through the environment configuration
1843
+ * (customProviders field) or programmatically via registerProvider/registerProviders.
1844
+ *
1845
+ * Custom registrations take precedence over built-in defaults.
1846
+ * Unknown providers receive an auto-generated fallback configuration.
1847
+ */
1848
+ class ProviderRegistryService {
1849
+ environment;
1850
+ customProviders = new Map();
1851
+ constructor(environment) {
1852
+ this.environment = environment;
1853
+ // Seed from environment customProviders if present
1854
+ if (this.environment.customProviders) {
1855
+ for (const [id, config] of Object.entries(this.environment.customProviders)) {
1856
+ this.customProviders.set(id, config);
1857
+ }
1858
+ }
1859
+ }
1860
+ /**
1861
+ * Register a custom OAuth provider configuration.
1862
+ * If a provider with the same id already exists, it will be overwritten.
1863
+ * @param id - Provider identifier (e.g., 'okta', 'auth0')
1864
+ * @param config - Provider display configuration
1865
+ */
1866
+ registerProvider(id, config) {
1867
+ this.customProviders.set(id, config);
1868
+ }
1869
+ /**
1870
+ * Register multiple custom OAuth provider configurations at once.
1871
+ * @param providers - Record of provider id to configuration
1872
+ */
1873
+ registerProviders(providers) {
1874
+ for (const [id, config] of Object.entries(providers)) {
1875
+ this.customProviders.set(id, config);
1876
+ }
1877
+ }
1878
+ /**
1879
+ * Get the full configuration for a provider.
1880
+ * Resolution order: custom registration > built-in default > auto-generated fallback.
1881
+ * @param provider - Provider identifier
1882
+ */
1883
+ getProviderConfig(provider) {
1884
+ // 1. Check custom registrations first
1885
+ const custom = this.customProviders.get(provider);
1886
+ if (custom) {
1887
+ return custom;
1888
+ }
1889
+ // 2. Check built-in providers
1890
+ const builtIn = BUILT_IN_PROVIDERS[provider];
1891
+ if (builtIn) {
1892
+ return builtIn;
1893
+ }
1894
+ // 3. Auto-generated fallback for unknown providers
1895
+ const displayName = provider.charAt(0).toUpperCase() + provider.slice(1);
1896
+ return {
1897
+ label: displayName,
1898
+ cssClass: `btn-${provider}`
1899
+ };
1900
+ }
1901
+ /**
1902
+ * Get the display label for a provider, formatted for sign-in context.
1903
+ * @param provider - Provider identifier
1904
+ * @returns Label like "Sign in with Google"
1905
+ */
1906
+ getLabel(provider) {
1907
+ const config = this.getProviderConfig(provider);
1908
+ if (provider === 'emailPassword') {
1909
+ return `Sign in with ${config.label}`;
1910
+ }
1911
+ return `Sign in with ${config.label}`;
1912
+ }
1913
+ /**
1914
+ * Get the display label for a provider, formatted for sign-up context.
1915
+ * @param provider - Provider identifier
1916
+ * @returns Label like "Sign up with Google"
1917
+ */
1918
+ getSignupLabel(provider) {
1919
+ const config = this.getProviderConfig(provider);
1920
+ return `Sign up with ${config.label}`;
1921
+ }
1922
+ /**
1923
+ * Get the icon for a provider, if configured.
1924
+ * @param provider - Provider identifier
1925
+ * @returns Icon string or undefined
1926
+ */
1927
+ getIcon(provider) {
1928
+ const config = this.getProviderConfig(provider);
1929
+ return config.icon;
1930
+ }
1931
+ /**
1932
+ * Get the CSS class for a provider button.
1933
+ * @param provider - Provider identifier
1934
+ * @returns CSS class string (e.g., "btn-google")
1935
+ */
1936
+ getCssClass(provider) {
1937
+ const config = this.getProviderConfig(provider);
1938
+ return config.cssClass || `btn-${provider}`;
1939
+ }
1940
+ /**
1941
+ * Get inline button styles for a provider, if configured.
1942
+ * @param provider - Provider identifier
1943
+ * @returns Style object for ngStyle binding, or null if no custom styles
1944
+ */
1945
+ getButtonStyle(provider) {
1946
+ const config = this.getProviderConfig(provider);
1947
+ if (!config.buttonStyle) {
1948
+ return null;
1949
+ }
1950
+ const styles = {};
1951
+ if (config.buttonStyle.borderColor) {
1952
+ styles['border-color'] = config.buttonStyle.borderColor;
1953
+ }
1954
+ if (config.buttonStyle.backgroundColor) {
1955
+ styles['background-color'] = config.buttonStyle.backgroundColor;
1956
+ }
1957
+ if (config.buttonStyle.color) {
1958
+ styles['color'] = config.buttonStyle.color;
1959
+ }
1960
+ return Object.keys(styles).length > 0 ? styles : null;
1961
+ }
1962
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ProviderRegistryService, deps: [{ token: MyEnvironmentModel }], target: i0.ɵɵFactoryTarget.Injectable });
1963
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ProviderRegistryService, providedIn: 'root' });
1964
+ }
1965
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ProviderRegistryService, decorators: [{
1966
+ type: Injectable,
1967
+ args: [{
1968
+ providedIn: 'root'
1969
+ }]
1970
+ }], ctorParameters: () => [{ type: MyEnvironmentModel, decorators: [{
1971
+ type: Inject,
1972
+ args: [MyEnvironmentModel]
1973
+ }] }] });
1974
+
1495
1975
  class NgxStoneScriptPhpClientModule {
1496
1976
  static forRoot(environment) {
1497
1977
  return {
@@ -1517,6 +1997,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
1517
1997
 
1518
1998
  class TenantLoginComponent {
1519
1999
  auth;
2000
+ providerRegistry;
1520
2001
  // Component Configuration
1521
2002
  title = 'Sign In';
1522
2003
  providers = ['google'];
@@ -1550,8 +2031,9 @@ class TenantLoginComponent {
1550
2031
  memberships = [];
1551
2032
  selectedTenantId = null;
1552
2033
  userName = '';
1553
- constructor(auth) {
2034
+ constructor(auth, providerRegistry) {
1554
2035
  this.auth = auth;
2036
+ this.providerRegistry = providerRegistry;
1555
2037
  }
1556
2038
  ngOnInit() {
1557
2039
  if (!this.providers || this.providers.length === 0) {
@@ -1573,22 +2055,16 @@ class TenantLoginComponent {
1573
2055
  return this.providers.includes(provider);
1574
2056
  }
1575
2057
  getProviderLabel(provider) {
1576
- const labels = {
1577
- google: 'Sign in with Google',
1578
- linkedin: 'Sign in with LinkedIn',
1579
- apple: 'Sign in with Apple',
1580
- microsoft: 'Sign in with Microsoft',
1581
- github: 'Sign in with GitHub',
1582
- zoho: 'Sign in with Zoho',
1583
- emailPassword: 'Sign in with Email'
1584
- };
1585
- return labels[provider];
2058
+ return this.providerRegistry.getLabel(provider);
1586
2059
  }
1587
2060
  getProviderIcon(provider) {
1588
- const icons = {
1589
- zoho: '🔶'
1590
- };
1591
- return icons[provider];
2061
+ return this.providerRegistry.getIcon(provider);
2062
+ }
2063
+ getProviderCssClass(provider) {
2064
+ return this.providerRegistry.getCssClass(provider);
2065
+ }
2066
+ getProviderButtonStyle(provider) {
2067
+ return this.providerRegistry.getButtonStyle(provider);
1592
2068
  }
1593
2069
  toggleAuthMethod(event) {
1594
2070
  event.preventDefault();
@@ -1746,7 +2222,7 @@ class TenantLoginComponent {
1746
2222
  event.preventDefault();
1747
2223
  this.createTenant.emit();
1748
2224
  }
1749
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TenantLoginComponent, deps: [{ token: AuthService }], target: i0.ɵɵFactoryTarget.Component });
2225
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TenantLoginComponent, deps: [{ token: AuthService }, { token: ProviderRegistryService }], target: i0.ɵɵFactoryTarget.Component });
1750
2226
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: TenantLoginComponent, isStandalone: true, selector: "lib-tenant-login", inputs: { title: "title", providers: "providers", showTenantSelector: "showTenantSelector", autoSelectSingleTenant: "autoSelectSingleTenant", prefillEmail: "prefillEmail", allowTenantCreation: "allowTenantCreation", tenantSelectorTitle: "tenantSelectorTitle", tenantSelectorDescription: "tenantSelectorDescription", continueButtonText: "continueButtonText", registerLinkText: "registerLinkText", registerLinkAction: "registerLinkAction", createTenantLinkText: "createTenantLinkText", createTenantLinkAction: "createTenantLinkAction" }, outputs: { tenantSelected: "tenantSelected", createTenant: "createTenant" }, ngImport: i0, template: `
1751
2227
  <div class="tenant-login-dialog">
1752
2228
  @if (!showingTenantSelector) {
@@ -1805,7 +2281,8 @@ class TenantLoginComponent {
1805
2281
  type="button"
1806
2282
  (click)="onOAuthLogin(provider)"
1807
2283
  [disabled]="loading"
1808
- class="btn btn-oauth btn-{{ provider }}">
2284
+ [class]="'btn btn-oauth ' + getProviderCssClass(provider)"
2285
+ [ngStyle]="getProviderButtonStyle(provider)">
1809
2286
  @if (getProviderIcon(provider)) {
1810
2287
  <span class="oauth-icon">
1811
2288
  {{ getProviderIcon(provider) }}
@@ -1912,7 +2389,7 @@ class TenantLoginComponent {
1912
2389
  </div>
1913
2390
  }
1914
2391
  </div>
1915
- `, isInline: true, styles: [".tenant-login-dialog{padding:24px;max-width:450px;position:relative}.login-title{margin:0 0 24px;font-size:24px;font-weight:500;text-align:center}.welcome-message{margin-bottom:16px;padding:12px;background:#e8f5e9;border-radius:4px;text-align:center;font-size:14px;color:#2e7d32}.selector-description{margin-bottom:20px;font-size:14px;color:#666;text-align:center}.email-form,.form-group{margin-bottom:16px}.password-group{position:relative}.form-control{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;font-size:14px;box-sizing:border-box}.password-input{padding-right:45px}.password-toggle{position:absolute;right:8px;top:50%;transform:translateY(-50%);background:none;border:none;cursor:pointer;font-size:18px;padding:8px;line-height:1;opacity:.6;transition:opacity .2s}.password-toggle:hover{opacity:1}.password-toggle:focus{outline:2px solid #4285f4;outline-offset:2px;border-radius:4px}.form-control:focus{outline:none;border-color:#4285f4}.btn{padding:12px 24px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-block{width:100%}.btn-primary{background-color:#4285f4;color:#fff}.btn-primary:hover:not(:disabled){background-color:#357ae8}.divider{margin:16px 0;text-align:center;position:relative}.divider:before{content:\"\";position:absolute;top:50%;left:0;right:0;height:1px;background:#ddd}.divider span{background:#fff;padding:0 12px;position:relative;color:#666;font-size:12px}.oauth-buttons{display:flex;flex-direction:column;gap:12px}.btn-oauth{width:100%;background:#fff;color:#333;border:1px solid #ddd;display:flex;align-items:center;justify-content:center;gap:8px}.btn-oauth:hover:not(:disabled){background:#f8f8f8}.btn-google{border-color:#4285f4}.btn-linkedin{border-color:#0077b5}.btn-apple{border-color:#000}.btn-microsoft{border-color:#00a4ef}.btn-github{border-color:#333}.btn-zoho{background-color:#f0483e;color:#fff;border:1px solid #d63b32}.btn-zoho:hover{background-color:#d63b32}.oauth-icon{font-size:18px}.switch-method{margin-top:12px;text-align:center;font-size:14px}.switch-method a{color:#4285f4;text-decoration:none}.switch-method a:hover{text-decoration:underline}.tenant-list{margin-bottom:20px;display:flex;flex-direction:column;gap:12px}.tenant-item{display:flex;align-items:flex-start;gap:12px;padding:16px;border:2px solid #e0e0e0;border-radius:6px;cursor:pointer;transition:all .2s}.tenant-item:hover{border-color:#4285f4;background:#f8f9ff}.tenant-item.selected{border-color:#4285f4;background:#e8f0fe}.tenant-radio{flex-shrink:0;padding-top:2px}.tenant-radio input[type=radio]{width:18px;height:18px;cursor:pointer}.tenant-info{flex:1}.tenant-name{font-size:16px;font-weight:500;color:#333;margin-bottom:4px}.tenant-meta{font-size:13px;color:#666}.tenant-role{font-weight:500;color:#4285f4}.tenant-separator{margin:0 6px}.error-message{margin-top:16px;padding:12px;background:#fee;color:#c33;border-radius:4px;font-size:14px}.loading-overlay{position:absolute;inset:0;background:#fffc;display:flex;align-items:center;justify-content:center}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #4285f4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.register-link{margin-top:16px;text-align:center;font-size:14px;color:#666}.register-link a{color:#4285f4;text-decoration:none}.register-link a:hover{text-decoration:underline}.create-tenant-link{margin-top:16px;padding-top:16px;border-top:1px solid #e0e0e0;text-align:center;font-size:14px;color:#666}.create-tenant-link a{color:#4285f4;text-decoration:none;font-weight:500}.create-tenant-link a:hover{text-decoration:underline}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }] });
2392
+ `, isInline: true, styles: [".tenant-login-dialog{padding:24px;max-width:450px;position:relative}.login-title{margin:0 0 24px;font-size:24px;font-weight:500;text-align:center}.welcome-message{margin-bottom:16px;padding:12px;background:#e8f5e9;border-radius:4px;text-align:center;font-size:14px;color:#2e7d32}.selector-description{margin-bottom:20px;font-size:14px;color:#666;text-align:center}.email-form,.form-group{margin-bottom:16px}.password-group{position:relative}.form-control{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;font-size:14px;box-sizing:border-box}.password-input{padding-right:45px}.password-toggle{position:absolute;right:8px;top:50%;transform:translateY(-50%);background:none;border:none;cursor:pointer;font-size:18px;padding:8px;line-height:1;opacity:.6;transition:opacity .2s}.password-toggle:hover{opacity:1}.password-toggle:focus{outline:2px solid #4285f4;outline-offset:2px;border-radius:4px}.form-control:focus{outline:none;border-color:#4285f4}.btn{padding:12px 24px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-block{width:100%}.btn-primary{background-color:#4285f4;color:#fff}.btn-primary:hover:not(:disabled){background-color:#357ae8}.divider{margin:16px 0;text-align:center;position:relative}.divider:before{content:\"\";position:absolute;top:50%;left:0;right:0;height:1px;background:#ddd}.divider span{background:#fff;padding:0 12px;position:relative;color:#666;font-size:12px}.oauth-buttons{display:flex;flex-direction:column;gap:12px}.btn-oauth{width:100%;background:#fff;color:#333;border:1px solid #ddd;display:flex;align-items:center;justify-content:center;gap:8px}.btn-oauth:hover:not(:disabled){background:#f8f8f8}.btn-google{border-color:#4285f4}.btn-linkedin{border-color:#0077b5}.btn-apple{border-color:#000}.btn-microsoft{border-color:#00a4ef}.btn-github{border-color:#333}.btn-zoho{background-color:#f0483e;color:#fff;border:1px solid #d63b32}.btn-zoho:hover{background-color:#d63b32}.oauth-icon{font-size:18px}.switch-method{margin-top:12px;text-align:center;font-size:14px}.switch-method a{color:#4285f4;text-decoration:none}.switch-method a:hover{text-decoration:underline}.tenant-list{margin-bottom:20px;display:flex;flex-direction:column;gap:12px}.tenant-item{display:flex;align-items:flex-start;gap:12px;padding:16px;border:2px solid #e0e0e0;border-radius:6px;cursor:pointer;transition:all .2s}.tenant-item:hover{border-color:#4285f4;background:#f8f9ff}.tenant-item.selected{border-color:#4285f4;background:#e8f0fe}.tenant-radio{flex-shrink:0;padding-top:2px}.tenant-radio input[type=radio]{width:18px;height:18px;cursor:pointer}.tenant-info{flex:1}.tenant-name{font-size:16px;font-weight:500;color:#333;margin-bottom:4px}.tenant-meta{font-size:13px;color:#666}.tenant-role{font-weight:500;color:#4285f4}.tenant-separator{margin:0 6px}.error-message{margin-top:16px;padding:12px;background:#fee;color:#c33;border-radius:4px;font-size:14px}.loading-overlay{position:absolute;inset:0;background:#fffc;display:flex;align-items:center;justify-content:center}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #4285f4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.register-link{margin-top:16px;text-align:center;font-size:14px;color:#666}.register-link a{color:#4285f4;text-decoration:none}.register-link a:hover{text-decoration:underline}.create-tenant-link{margin-top:16px;padding-top:16px;border-top:1px solid #e0e0e0;text-align:center;font-size:14px;color:#666}.create-tenant-link a{color:#4285f4;text-decoration:none;font-weight:500}.create-tenant-link a:hover{text-decoration:underline}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i4.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i4.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }] });
1916
2393
  }
1917
2394
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TenantLoginComponent, decorators: [{
1918
2395
  type: Component,
@@ -1974,7 +2451,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
1974
2451
  type="button"
1975
2452
  (click)="onOAuthLogin(provider)"
1976
2453
  [disabled]="loading"
1977
- class="btn btn-oauth btn-{{ provider }}">
2454
+ [class]="'btn btn-oauth ' + getProviderCssClass(provider)"
2455
+ [ngStyle]="getProviderButtonStyle(provider)">
1978
2456
  @if (getProviderIcon(provider)) {
1979
2457
  <span class="oauth-icon">
1980
2458
  {{ getProviderIcon(provider) }}
@@ -2082,7 +2560,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
2082
2560
  }
2083
2561
  </div>
2084
2562
  `, styles: [".tenant-login-dialog{padding:24px;max-width:450px;position:relative}.login-title{margin:0 0 24px;font-size:24px;font-weight:500;text-align:center}.welcome-message{margin-bottom:16px;padding:12px;background:#e8f5e9;border-radius:4px;text-align:center;font-size:14px;color:#2e7d32}.selector-description{margin-bottom:20px;font-size:14px;color:#666;text-align:center}.email-form,.form-group{margin-bottom:16px}.password-group{position:relative}.form-control{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;font-size:14px;box-sizing:border-box}.password-input{padding-right:45px}.password-toggle{position:absolute;right:8px;top:50%;transform:translateY(-50%);background:none;border:none;cursor:pointer;font-size:18px;padding:8px;line-height:1;opacity:.6;transition:opacity .2s}.password-toggle:hover{opacity:1}.password-toggle:focus{outline:2px solid #4285f4;outline-offset:2px;border-radius:4px}.form-control:focus{outline:none;border-color:#4285f4}.btn{padding:12px 24px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-block{width:100%}.btn-primary{background-color:#4285f4;color:#fff}.btn-primary:hover:not(:disabled){background-color:#357ae8}.divider{margin:16px 0;text-align:center;position:relative}.divider:before{content:\"\";position:absolute;top:50%;left:0;right:0;height:1px;background:#ddd}.divider span{background:#fff;padding:0 12px;position:relative;color:#666;font-size:12px}.oauth-buttons{display:flex;flex-direction:column;gap:12px}.btn-oauth{width:100%;background:#fff;color:#333;border:1px solid #ddd;display:flex;align-items:center;justify-content:center;gap:8px}.btn-oauth:hover:not(:disabled){background:#f8f8f8}.btn-google{border-color:#4285f4}.btn-linkedin{border-color:#0077b5}.btn-apple{border-color:#000}.btn-microsoft{border-color:#00a4ef}.btn-github{border-color:#333}.btn-zoho{background-color:#f0483e;color:#fff;border:1px solid #d63b32}.btn-zoho:hover{background-color:#d63b32}.oauth-icon{font-size:18px}.switch-method{margin-top:12px;text-align:center;font-size:14px}.switch-method a{color:#4285f4;text-decoration:none}.switch-method a:hover{text-decoration:underline}.tenant-list{margin-bottom:20px;display:flex;flex-direction:column;gap:12px}.tenant-item{display:flex;align-items:flex-start;gap:12px;padding:16px;border:2px solid #e0e0e0;border-radius:6px;cursor:pointer;transition:all .2s}.tenant-item:hover{border-color:#4285f4;background:#f8f9ff}.tenant-item.selected{border-color:#4285f4;background:#e8f0fe}.tenant-radio{flex-shrink:0;padding-top:2px}.tenant-radio input[type=radio]{width:18px;height:18px;cursor:pointer}.tenant-info{flex:1}.tenant-name{font-size:16px;font-weight:500;color:#333;margin-bottom:4px}.tenant-meta{font-size:13px;color:#666}.tenant-role{font-weight:500;color:#4285f4}.tenant-separator{margin:0 6px}.error-message{margin-top:16px;padding:12px;background:#fee;color:#c33;border-radius:4px;font-size:14px}.loading-overlay{position:absolute;inset:0;background:#fffc;display:flex;align-items:center;justify-content:center}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #4285f4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.register-link{margin-top:16px;text-align:center;font-size:14px;color:#666}.register-link a{color:#4285f4;text-decoration:none}.register-link a:hover{text-decoration:underline}.create-tenant-link{margin-top:16px;padding-top:16px;border-top:1px solid #e0e0e0;text-align:center;font-size:14px;color:#666}.create-tenant-link a{color:#4285f4;text-decoration:none;font-weight:500}.create-tenant-link a:hover{text-decoration:underline}\n"] }]
2085
- }], ctorParameters: () => [{ type: AuthService }], propDecorators: { title: [{
2563
+ }], ctorParameters: () => [{ type: AuthService }, { type: ProviderRegistryService }], propDecorators: { title: [{
2086
2564
  type: Input
2087
2565
  }], providers: [{
2088
2566
  type: Input
@@ -2346,7 +2824,7 @@ class RegisterComponent {
2346
2824
  <a href="#" (click)="onLoginClick($event)">Sign in</a>
2347
2825
  </div>
2348
2826
  </div>
2349
- `, isInline: true, styles: [".register-dialog{padding:24px;max-width:400px;position:relative}.register-title{margin:0 0 24px;font-size:24px;font-weight:500;text-align:center}.register-form,.form-group{margin-bottom:16px}.password-group{position:relative}.form-group label{display:block;margin-bottom:6px;font-size:14px;font-weight:500;color:#333}.form-control{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;font-size:14px;box-sizing:border-box}.password-input{padding-right:45px}.password-toggle{position:absolute;right:8px;top:38px;background:none;border:none;cursor:pointer;font-size:18px;padding:8px;line-height:1;opacity:.6;transition:opacity .2s}.password-toggle:hover{opacity:1}.password-toggle:focus{outline:2px solid #4285f4;outline-offset:2px;border-radius:4px}.form-control:focus{outline:none;border-color:#4285f4}.form-hint{display:block;margin-top:4px;font-size:12px;color:#666}.btn{padding:12px 24px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-block{width:100%}.btn-primary{background-color:#4285f4;color:#fff}.btn-primary:hover:not(:disabled){background-color:#357ae8}.error-message{margin-top:16px;padding:12px;background:#fee;color:#c33;border-radius:4px;font-size:14px}.success-message{margin-top:16px;padding:12px;background:#efe;color:#3a3;border-radius:4px;font-size:14px}.loading-overlay{position:absolute;inset:0;background:#fffc;display:flex;align-items:center;justify-content:center}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #4285f4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.login-link{margin-top:16px;text-align:center;font-size:14px;color:#666}.login-link a{color:#4285f4;text-decoration:none}.login-link a:hover{text-decoration:underline}.account-link-prompt{background:#f8f9fa;border:2px solid #4285f4;border-radius:8px;padding:24px;margin-bottom:16px;text-align:center}.prompt-icon{font-size:48px;margin-bottom:12px}.account-link-prompt h3{margin:0 0 12px;color:#333;font-size:20px;font-weight:500}.account-link-prompt p{margin:8px 0;color:#555;font-size:14px;line-height:1.6}.prompt-actions{margin-top:20px;display:flex;flex-direction:column;gap:10px}.btn-secondary{background:#fff;color:#333;border:1px solid #ddd}.btn-secondary:hover:not(:disabled){background:#f8f9fa;border-color:#ccc}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2.MinLengthValidator, selector: "[minlength][formControlName],[minlength][formControl],[minlength][ngModel]", inputs: ["minlength"] }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }] });
2827
+ `, isInline: true, styles: [".register-dialog{padding:24px;max-width:400px;position:relative}.register-title{margin:0 0 24px;font-size:24px;font-weight:500;text-align:center}.register-form,.form-group{margin-bottom:16px}.password-group{position:relative}.form-group label{display:block;margin-bottom:6px;font-size:14px;font-weight:500;color:#333}.form-control{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;font-size:14px;box-sizing:border-box}.password-input{padding-right:45px}.password-toggle{position:absolute;right:8px;top:38px;background:none;border:none;cursor:pointer;font-size:18px;padding:8px;line-height:1;opacity:.6;transition:opacity .2s}.password-toggle:hover{opacity:1}.password-toggle:focus{outline:2px solid #4285f4;outline-offset:2px;border-radius:4px}.form-control:focus{outline:none;border-color:#4285f4}.form-hint{display:block;margin-top:4px;font-size:12px;color:#666}.btn{padding:12px 24px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-block{width:100%}.btn-primary{background-color:#4285f4;color:#fff}.btn-primary:hover:not(:disabled){background-color:#357ae8}.error-message{margin-top:16px;padding:12px;background:#fee;color:#c33;border-radius:4px;font-size:14px}.success-message{margin-top:16px;padding:12px;background:#efe;color:#3a3;border-radius:4px;font-size:14px}.loading-overlay{position:absolute;inset:0;background:#fffc;display:flex;align-items:center;justify-content:center}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #4285f4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.login-link{margin-top:16px;text-align:center;font-size:14px;color:#666}.login-link a{color:#4285f4;text-decoration:none}.login-link a:hover{text-decoration:underline}.account-link-prompt{background:#f8f9fa;border:2px solid #4285f4;border-radius:8px;padding:24px;margin-bottom:16px;text-align:center}.prompt-icon{font-size:48px;margin-bottom:12px}.account-link-prompt h3{margin:0 0 12px;color:#333;font-size:20px;font-weight:500}.account-link-prompt p{margin:8px 0;color:#555;font-size:14px;line-height:1.6}.prompt-actions{margin-top:20px;display:flex;flex-direction:column;gap:10px}.btn-secondary{background:#fff;color:#333;border:1px solid #ddd}.btn-secondary:hover:not(:disabled){background:#f8f9fa;border-color:#ccc}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i4.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i4.MinLengthValidator, selector: "[minlength][formControlName],[minlength][formControl],[minlength][ngModel]", inputs: ["minlength"] }, { kind: "directive", type: i4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i4.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }] });
2350
2828
  }
2351
2829
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: RegisterComponent, decorators: [{
2352
2830
  type: Component,
@@ -2601,6 +3079,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
2601
3079
 
2602
3080
  class LoginDialogComponent {
2603
3081
  auth;
3082
+ providerRegistry;
2604
3083
  /**
2605
3084
  * REQUIRED: Which authentication providers to show in this dialog
2606
3085
  * @example ['google', 'linkedin', 'emailPassword']
@@ -2612,8 +3091,9 @@ class LoginDialogComponent {
2612
3091
  loading = false;
2613
3092
  showPassword = false;
2614
3093
  oauthProviders = [];
2615
- constructor(auth) {
3094
+ constructor(auth, providerRegistry) {
2616
3095
  this.auth = auth;
3096
+ this.providerRegistry = providerRegistry;
2617
3097
  }
2618
3098
  ngOnInit() {
2619
3099
  if (!this.providers || this.providers.length === 0) {
@@ -2628,20 +3108,16 @@ class LoginDialogComponent {
2628
3108
  return this.providers.includes(provider);
2629
3109
  }
2630
3110
  getProviderLabel(provider) {
2631
- const labels = {
2632
- google: 'Sign in with Google',
2633
- linkedin: 'Sign in with LinkedIn',
2634
- apple: 'Sign in with Apple',
2635
- microsoft: 'Sign in with Microsoft',
2636
- github: 'Sign in with GitHub',
2637
- zoho: 'Sign in with Zoho',
2638
- emailPassword: 'Sign in with Email'
2639
- };
2640
- return labels[provider];
3111
+ return this.providerRegistry.getLabel(provider);
2641
3112
  }
2642
3113
  getProviderIcon(provider) {
2643
- // Platforms can customize icons via CSS classes: .btn-google, .btn-linkedin, etc.
2644
- return undefined;
3114
+ return this.providerRegistry.getIcon(provider);
3115
+ }
3116
+ getProviderCssClass(provider) {
3117
+ return this.providerRegistry.getCssClass(provider);
3118
+ }
3119
+ getProviderButtonStyle(provider) {
3120
+ return this.providerRegistry.getButtonStyle(provider);
2645
3121
  }
2646
3122
  async onEmailLogin() {
2647
3123
  if (!this.email || !this.password) {
@@ -2687,7 +3163,7 @@ class LoginDialogComponent {
2687
3163
  // For now, just emit a console message
2688
3164
  console.log('Register clicked - platform should handle navigation');
2689
3165
  }
2690
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: LoginDialogComponent, deps: [{ token: AuthService }], target: i0.ɵɵFactoryTarget.Component });
3166
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: LoginDialogComponent, deps: [{ token: AuthService }, { token: ProviderRegistryService }], target: i0.ɵɵFactoryTarget.Component });
2691
3167
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: LoginDialogComponent, isStandalone: true, selector: "lib-login-dialog", inputs: { providers: "providers" }, ngImport: i0, template: `
2692
3168
  <div class="login-dialog">
2693
3169
  <h2 class="login-title">Sign In</h2>
@@ -2743,7 +3219,8 @@ class LoginDialogComponent {
2743
3219
  <button
2744
3220
  (click)="onOAuthLogin(provider)"
2745
3221
  [disabled]="loading"
2746
- class="btn btn-oauth btn-{{ provider }}">
3222
+ [class]="'btn btn-oauth ' + getProviderCssClass(provider)"
3223
+ [ngStyle]="getProviderButtonStyle(provider)">
2747
3224
  @if (getProviderIcon(provider)) {
2748
3225
  <span class="oauth-icon">
2749
3226
  {{ getProviderIcon(provider) }}
@@ -2775,7 +3252,7 @@ class LoginDialogComponent {
2775
3252
  <a href="#" (click)="onRegisterClick($event)">Sign up</a>
2776
3253
  </div>
2777
3254
  </div>
2778
- `, isInline: true, styles: [".login-dialog{padding:24px;max-width:400px;position:relative}.login-title{margin:0 0 24px;font-size:24px;font-weight:500;text-align:center}.email-form,.form-group{margin-bottom:16px}.password-group{position:relative}.form-control{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;font-size:14px;box-sizing:border-box}.password-input{padding-right:45px}.password-toggle{position:absolute;right:8px;top:50%;transform:translateY(-50%);background:none;border:none;cursor:pointer;font-size:18px;padding:8px;line-height:1;opacity:.6;transition:opacity .2s}.password-toggle:hover{opacity:1}.password-toggle:focus{outline:2px solid #4285f4;outline-offset:2px;border-radius:4px}.form-control:focus{outline:none;border-color:#4285f4}.btn{padding:12px 24px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-block{width:100%}.btn-primary{background-color:#4285f4;color:#fff}.btn-primary:hover:not(:disabled){background-color:#357ae8}.divider{margin:16px 0;text-align:center;position:relative}.divider:before{content:\"\";position:absolute;top:50%;left:0;right:0;height:1px;background:#ddd}.divider span{background:#fff;padding:0 12px;position:relative;color:#666;font-size:12px}.oauth-buttons{display:flex;flex-direction:column;gap:12px}.btn-oauth{width:100%;background:#fff;color:#333;border:1px solid #ddd;display:flex;align-items:center;justify-content:center;gap:8px}.btn-oauth:hover:not(:disabled){background:#f8f8f8}.btn-google{border-color:#4285f4}.btn-linkedin{border-color:#0077b5}.btn-apple{border-color:#000}.btn-microsoft{border-color:#00a4ef}.btn-github{border-color:#333}.oauth-icon{font-size:18px}.error-message{margin-top:16px;padding:12px;background:#fee;color:#c33;border-radius:4px;font-size:14px}.loading-overlay{position:absolute;inset:0;background:#fffc;display:flex;align-items:center;justify-content:center}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #4285f4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.register-link{margin-top:16px;text-align:center;font-size:14px;color:#666}.register-link a{color:#4285f4;text-decoration:none}.register-link a:hover{text-decoration:underline}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }] });
3255
+ `, isInline: true, styles: [".login-dialog{padding:24px;max-width:400px;position:relative}.login-title{margin:0 0 24px;font-size:24px;font-weight:500;text-align:center}.email-form,.form-group{margin-bottom:16px}.password-group{position:relative}.form-control{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;font-size:14px;box-sizing:border-box}.password-input{padding-right:45px}.password-toggle{position:absolute;right:8px;top:50%;transform:translateY(-50%);background:none;border:none;cursor:pointer;font-size:18px;padding:8px;line-height:1;opacity:.6;transition:opacity .2s}.password-toggle:hover{opacity:1}.password-toggle:focus{outline:2px solid #4285f4;outline-offset:2px;border-radius:4px}.form-control:focus{outline:none;border-color:#4285f4}.btn{padding:12px 24px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-block{width:100%}.btn-primary{background-color:#4285f4;color:#fff}.btn-primary:hover:not(:disabled){background-color:#357ae8}.divider{margin:16px 0;text-align:center;position:relative}.divider:before{content:\"\";position:absolute;top:50%;left:0;right:0;height:1px;background:#ddd}.divider span{background:#fff;padding:0 12px;position:relative;color:#666;font-size:12px}.oauth-buttons{display:flex;flex-direction:column;gap:12px}.btn-oauth{width:100%;background:#fff;color:#333;border:1px solid #ddd;display:flex;align-items:center;justify-content:center;gap:8px}.btn-oauth:hover:not(:disabled){background:#f8f8f8}.btn-google{border-color:#4285f4}.btn-linkedin{border-color:#0077b5}.btn-apple{border-color:#000}.btn-microsoft{border-color:#00a4ef}.btn-github{border-color:#333}.oauth-icon{font-size:18px}.error-message{margin-top:16px;padding:12px;background:#fee;color:#c33;border-radius:4px;font-size:14px}.loading-overlay{position:absolute;inset:0;background:#fffc;display:flex;align-items:center;justify-content:center}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #4285f4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.register-link{margin-top:16px;text-align:center;font-size:14px;color:#666}.register-link a{color:#4285f4;text-decoration:none}.register-link a:hover{text-decoration:underline}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i4.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i4.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }] });
2779
3256
  }
2780
3257
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: LoginDialogComponent, decorators: [{
2781
3258
  type: Component,
@@ -2834,7 +3311,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
2834
3311
  <button
2835
3312
  (click)="onOAuthLogin(provider)"
2836
3313
  [disabled]="loading"
2837
- class="btn btn-oauth btn-{{ provider }}">
3314
+ [class]="'btn btn-oauth ' + getProviderCssClass(provider)"
3315
+ [ngStyle]="getProviderButtonStyle(provider)">
2838
3316
  @if (getProviderIcon(provider)) {
2839
3317
  <span class="oauth-icon">
2840
3318
  {{ getProviderIcon(provider) }}
@@ -2867,12 +3345,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
2867
3345
  </div>
2868
3346
  </div>
2869
3347
  `, styles: [".login-dialog{padding:24px;max-width:400px;position:relative}.login-title{margin:0 0 24px;font-size:24px;font-weight:500;text-align:center}.email-form,.form-group{margin-bottom:16px}.password-group{position:relative}.form-control{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;font-size:14px;box-sizing:border-box}.password-input{padding-right:45px}.password-toggle{position:absolute;right:8px;top:50%;transform:translateY(-50%);background:none;border:none;cursor:pointer;font-size:18px;padding:8px;line-height:1;opacity:.6;transition:opacity .2s}.password-toggle:hover{opacity:1}.password-toggle:focus{outline:2px solid #4285f4;outline-offset:2px;border-radius:4px}.form-control:focus{outline:none;border-color:#4285f4}.btn{padding:12px 24px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-block{width:100%}.btn-primary{background-color:#4285f4;color:#fff}.btn-primary:hover:not(:disabled){background-color:#357ae8}.divider{margin:16px 0;text-align:center;position:relative}.divider:before{content:\"\";position:absolute;top:50%;left:0;right:0;height:1px;background:#ddd}.divider span{background:#fff;padding:0 12px;position:relative;color:#666;font-size:12px}.oauth-buttons{display:flex;flex-direction:column;gap:12px}.btn-oauth{width:100%;background:#fff;color:#333;border:1px solid #ddd;display:flex;align-items:center;justify-content:center;gap:8px}.btn-oauth:hover:not(:disabled){background:#f8f8f8}.btn-google{border-color:#4285f4}.btn-linkedin{border-color:#0077b5}.btn-apple{border-color:#000}.btn-microsoft{border-color:#00a4ef}.btn-github{border-color:#333}.oauth-icon{font-size:18px}.error-message{margin-top:16px;padding:12px;background:#fee;color:#c33;border-radius:4px;font-size:14px}.loading-overlay{position:absolute;inset:0;background:#fffc;display:flex;align-items:center;justify-content:center}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #4285f4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.register-link{margin-top:16px;text-align:center;font-size:14px;color:#666}.register-link a{color:#4285f4;text-decoration:none}.register-link a:hover{text-decoration:underline}\n"] }]
2870
- }], ctorParameters: () => [{ type: AuthService }], propDecorators: { providers: [{
3348
+ }], ctorParameters: () => [{ type: AuthService }, { type: ProviderRegistryService }], propDecorators: { providers: [{
2871
3349
  type: Input
2872
3350
  }] } });
2873
3351
 
2874
3352
  class TenantRegisterComponent {
2875
3353
  auth;
3354
+ providerRegistry;
2876
3355
  // Component Configuration
2877
3356
  title = 'Create New Organization';
2878
3357
  providers = ['google'];
@@ -2917,8 +3396,9 @@ class TenantRegisterComponent {
2917
3396
  oauthProviders = [];
2918
3397
  showPassword = false;
2919
3398
  showConfirmPassword = false;
2920
- constructor(auth) {
3399
+ constructor(auth, providerRegistry) {
2921
3400
  this.auth = auth;
3401
+ this.providerRegistry = providerRegistry;
2922
3402
  }
2923
3403
  ngOnInit() {
2924
3404
  if (!this.providers || this.providers.length === 0) {
@@ -2935,19 +3415,16 @@ class TenantRegisterComponent {
2935
3415
  return this.providers.includes(provider);
2936
3416
  }
2937
3417
  getProviderLabel(provider) {
2938
- const labels = {
2939
- google: 'Sign up with Google',
2940
- linkedin: 'Sign up with LinkedIn',
2941
- apple: 'Sign up with Apple',
2942
- microsoft: 'Sign up with Microsoft',
2943
- github: 'Sign up with GitHub',
2944
- zoho: 'Sign up with Zoho',
2945
- emailPassword: 'Sign up with Email'
2946
- };
2947
- return labels[provider];
3418
+ return this.providerRegistry.getSignupLabel(provider);
2948
3419
  }
2949
3420
  getProviderIcon(provider) {
2950
- return undefined;
3421
+ return this.providerRegistry.getIcon(provider);
3422
+ }
3423
+ getProviderCssClass(provider) {
3424
+ return this.providerRegistry.getCssClass(provider);
3425
+ }
3426
+ getProviderButtonStyle(provider) {
3427
+ return this.providerRegistry.getButtonStyle(provider);
2951
3428
  }
2952
3429
  onTenantNameChange() {
2953
3430
  // Auto-generate slug from tenant name
@@ -3093,7 +3570,7 @@ class TenantRegisterComponent {
3093
3570
  event.preventDefault();
3094
3571
  this.navigateToLogin.emit();
3095
3572
  }
3096
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TenantRegisterComponent, deps: [{ token: AuthService }], target: i0.ɵɵFactoryTarget.Component });
3573
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TenantRegisterComponent, deps: [{ token: AuthService }, { token: ProviderRegistryService }], target: i0.ɵɵFactoryTarget.Component });
3097
3574
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: TenantRegisterComponent, isStandalone: true, selector: "lib-tenant-register", inputs: { title: "title", providers: "providers", requireTenantName: "requireTenantName", tenantSectionTitle: "tenantSectionTitle", tenantNameLabel: "tenantNameLabel", tenantNamePlaceholder: "tenantNamePlaceholder", tenantSlugLabel: "tenantSlugLabel", tenantSlugPlaceholder: "tenantSlugPlaceholder", urlPreviewEnabled: "urlPreviewEnabled", urlPreviewPrefix: "urlPreviewPrefix", userSectionTitle: "userSectionTitle", oauthDescription: "oauthDescription", ownershipTitle: "ownershipTitle", ownershipMessage: "ownershipMessage", submitButtonText: "submitButtonText", loginLinkText: "loginLinkText", loginLinkAction: "loginLinkAction" }, outputs: { tenantCreated: "tenantCreated", navigateToLogin: "navigateToLogin" }, ngImport: i0, template: `
3098
3575
  <div class="tenant-register-dialog">
3099
3576
  <h2 class="register-title">{{ title }}</h2>
@@ -3167,7 +3644,8 @@ class TenantRegisterComponent {
3167
3644
  type="button"
3168
3645
  (click)="onOAuthRegister(provider)"
3169
3646
  [disabled]="loading || !isFormValid()"
3170
- class="btn btn-oauth btn-{{ provider }}">
3647
+ [class]="'btn btn-oauth ' + getProviderCssClass(provider)"
3648
+ [ngStyle]="getProviderButtonStyle(provider)">
3171
3649
  @if (getProviderIcon(provider)) {
3172
3650
  <span class="oauth-icon">
3173
3651
  {{ getProviderIcon(provider) }}
@@ -3301,7 +3779,7 @@ class TenantRegisterComponent {
3301
3779
  <a href="#" (click)="onLoginClick($event)">{{ loginLinkAction }}</a>
3302
3780
  </div>
3303
3781
  </div>
3304
- `, isInline: true, styles: [".tenant-register-dialog{padding:24px;max-width:500px;position:relative}.register-title{margin:0 0 20px;font-size:24px;font-weight:500;text-align:center}.warning-box{display:flex;gap:12px;padding:16px;background:#fff3cd;border:1px solid #ffc107;border-radius:6px;margin-bottom:24px}.warning-icon{font-size:24px;line-height:1}.warning-content{flex:1}.warning-content strong{display:block;margin-bottom:4px;color:#856404;font-size:14px}.warning-content p{margin:0;color:#856404;font-size:13px;line-height:1.5}.section-header{font-size:16px;font-weight:600;margin:20px 0 12px;padding-bottom:8px;border-bottom:2px solid #e0e0e0;color:#333}.register-form,.form-group{margin-bottom:16px}.password-group{position:relative}.form-group label{display:block;margin-bottom:6px;font-size:14px;font-weight:500;color:#333}.form-control{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;font-size:14px;box-sizing:border-box;transition:border-color .2s}.password-input{padding-right:45px}.password-toggle{position:absolute;right:8px;top:38px;background:none;border:none;cursor:pointer;font-size:18px;padding:8px;line-height:1;opacity:.6;transition:opacity .2s}.password-toggle:hover{opacity:1}.password-toggle:focus{outline:2px solid #4285f4;outline-offset:2px;border-radius:4px}.form-control:focus{outline:none;border-color:#4285f4}.form-control.input-error{border-color:#dc3545}.form-control.input-success{border-color:#28a745}.form-hint{display:block;margin-top:4px;font-size:12px;color:#666}.form-hint .checking{color:#666}.form-hint .available{color:#28a745;font-weight:500}.form-hint .error-text{color:#dc3545}.oauth-section{margin:16px 0}.oauth-description{margin-bottom:12px;font-size:14px;color:#666;text-align:center}.oauth-buttons{display:flex;flex-direction:column;gap:12px}.btn{padding:12px 24px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s,opacity .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-block{width:100%}.btn-primary{background-color:#4285f4;color:#fff}.btn-primary:hover:not(:disabled){background-color:#357ae8}.btn-oauth{width:100%;background:#fff;color:#333;border:1px solid #ddd;display:flex;align-items:center;justify-content:center;gap:8px}.btn-oauth:hover:not(:disabled){background:#f8f8f8}.btn-google{border-color:#4285f4}.btn-linkedin{border-color:#0077b5}.btn-apple{border-color:#000}.btn-microsoft{border-color:#00a4ef}.btn-github{border-color:#333}.oauth-icon{font-size:18px}.switch-method{margin-top:12px;text-align:center;font-size:14px}.switch-method a{color:#4285f4;text-decoration:none}.switch-method a:hover{text-decoration:underline}.error-message{margin-top:16px;padding:12px;background:#fee;color:#c33;border-radius:4px;font-size:14px}.success-message{margin-top:16px;padding:12px;background:#efe;color:#3a3;border-radius:4px;font-size:14px}.loading-overlay{position:absolute;inset:0;background:#fffffff2;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #4285f4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.loading-text{margin:0;font-size:14px;color:#666}.login-link{margin-top:16px;text-align:center;font-size:14px;color:#666}.login-link a{color:#4285f4;text-decoration:none}.login-link a:hover{text-decoration:underline}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2.MinLengthValidator, selector: "[minlength][formControlName],[minlength][formControl],[minlength][ngModel]", inputs: ["minlength"] }, { kind: "directive", type: i2.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }] });
3782
+ `, isInline: true, styles: [".tenant-register-dialog{padding:24px;max-width:500px;position:relative}.register-title{margin:0 0 20px;font-size:24px;font-weight:500;text-align:center}.warning-box{display:flex;gap:12px;padding:16px;background:#fff3cd;border:1px solid #ffc107;border-radius:6px;margin-bottom:24px}.warning-icon{font-size:24px;line-height:1}.warning-content{flex:1}.warning-content strong{display:block;margin-bottom:4px;color:#856404;font-size:14px}.warning-content p{margin:0;color:#856404;font-size:13px;line-height:1.5}.section-header{font-size:16px;font-weight:600;margin:20px 0 12px;padding-bottom:8px;border-bottom:2px solid #e0e0e0;color:#333}.register-form,.form-group{margin-bottom:16px}.password-group{position:relative}.form-group label{display:block;margin-bottom:6px;font-size:14px;font-weight:500;color:#333}.form-control{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;font-size:14px;box-sizing:border-box;transition:border-color .2s}.password-input{padding-right:45px}.password-toggle{position:absolute;right:8px;top:38px;background:none;border:none;cursor:pointer;font-size:18px;padding:8px;line-height:1;opacity:.6;transition:opacity .2s}.password-toggle:hover{opacity:1}.password-toggle:focus{outline:2px solid #4285f4;outline-offset:2px;border-radius:4px}.form-control:focus{outline:none;border-color:#4285f4}.form-control.input-error{border-color:#dc3545}.form-control.input-success{border-color:#28a745}.form-hint{display:block;margin-top:4px;font-size:12px;color:#666}.form-hint .checking{color:#666}.form-hint .available{color:#28a745;font-weight:500}.form-hint .error-text{color:#dc3545}.oauth-section{margin:16px 0}.oauth-description{margin-bottom:12px;font-size:14px;color:#666;text-align:center}.oauth-buttons{display:flex;flex-direction:column;gap:12px}.btn{padding:12px 24px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s,opacity .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-block{width:100%}.btn-primary{background-color:#4285f4;color:#fff}.btn-primary:hover:not(:disabled){background-color:#357ae8}.btn-oauth{width:100%;background:#fff;color:#333;border:1px solid #ddd;display:flex;align-items:center;justify-content:center;gap:8px}.btn-oauth:hover:not(:disabled){background:#f8f8f8}.btn-google{border-color:#4285f4}.btn-linkedin{border-color:#0077b5}.btn-apple{border-color:#000}.btn-microsoft{border-color:#00a4ef}.btn-github{border-color:#333}.oauth-icon{font-size:18px}.switch-method{margin-top:12px;text-align:center;font-size:14px}.switch-method a{color:#4285f4;text-decoration:none}.switch-method a:hover{text-decoration:underline}.error-message{margin-top:16px;padding:12px;background:#fee;color:#c33;border-radius:4px;font-size:14px}.success-message{margin-top:16px;padding:12px;background:#efe;color:#3a3;border-radius:4px;font-size:14px}.loading-overlay{position:absolute;inset:0;background:#fffffff2;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #4285f4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.loading-text{margin:0;font-size:14px;color:#666}.login-link{margin-top:16px;text-align:center;font-size:14px;color:#666}.login-link a{color:#4285f4;text-decoration:none}.login-link a:hover{text-decoration:underline}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i4.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i4.MinLengthValidator, selector: "[minlength][formControlName],[minlength][formControl],[minlength][ngModel]", inputs: ["minlength"] }, { kind: "directive", type: i4.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { kind: "directive", type: i4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i4.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }] });
3305
3783
  }
3306
3784
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TenantRegisterComponent, decorators: [{
3307
3785
  type: Component,
@@ -3378,7 +3856,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
3378
3856
  type="button"
3379
3857
  (click)="onOAuthRegister(provider)"
3380
3858
  [disabled]="loading || !isFormValid()"
3381
- class="btn btn-oauth btn-{{ provider }}">
3859
+ [class]="'btn btn-oauth ' + getProviderCssClass(provider)"
3860
+ [ngStyle]="getProviderButtonStyle(provider)">
3382
3861
  @if (getProviderIcon(provider)) {
3383
3862
  <span class="oauth-icon">
3384
3863
  {{ getProviderIcon(provider) }}
@@ -3513,7 +3992,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
3513
3992
  </div>
3514
3993
  </div>
3515
3994
  `, styles: [".tenant-register-dialog{padding:24px;max-width:500px;position:relative}.register-title{margin:0 0 20px;font-size:24px;font-weight:500;text-align:center}.warning-box{display:flex;gap:12px;padding:16px;background:#fff3cd;border:1px solid #ffc107;border-radius:6px;margin-bottom:24px}.warning-icon{font-size:24px;line-height:1}.warning-content{flex:1}.warning-content strong{display:block;margin-bottom:4px;color:#856404;font-size:14px}.warning-content p{margin:0;color:#856404;font-size:13px;line-height:1.5}.section-header{font-size:16px;font-weight:600;margin:20px 0 12px;padding-bottom:8px;border-bottom:2px solid #e0e0e0;color:#333}.register-form,.form-group{margin-bottom:16px}.password-group{position:relative}.form-group label{display:block;margin-bottom:6px;font-size:14px;font-weight:500;color:#333}.form-control{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;font-size:14px;box-sizing:border-box;transition:border-color .2s}.password-input{padding-right:45px}.password-toggle{position:absolute;right:8px;top:38px;background:none;border:none;cursor:pointer;font-size:18px;padding:8px;line-height:1;opacity:.6;transition:opacity .2s}.password-toggle:hover{opacity:1}.password-toggle:focus{outline:2px solid #4285f4;outline-offset:2px;border-radius:4px}.form-control:focus{outline:none;border-color:#4285f4}.form-control.input-error{border-color:#dc3545}.form-control.input-success{border-color:#28a745}.form-hint{display:block;margin-top:4px;font-size:12px;color:#666}.form-hint .checking{color:#666}.form-hint .available{color:#28a745;font-weight:500}.form-hint .error-text{color:#dc3545}.oauth-section{margin:16px 0}.oauth-description{margin-bottom:12px;font-size:14px;color:#666;text-align:center}.oauth-buttons{display:flex;flex-direction:column;gap:12px}.btn{padding:12px 24px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s,opacity .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-block{width:100%}.btn-primary{background-color:#4285f4;color:#fff}.btn-primary:hover:not(:disabled){background-color:#357ae8}.btn-oauth{width:100%;background:#fff;color:#333;border:1px solid #ddd;display:flex;align-items:center;justify-content:center;gap:8px}.btn-oauth:hover:not(:disabled){background:#f8f8f8}.btn-google{border-color:#4285f4}.btn-linkedin{border-color:#0077b5}.btn-apple{border-color:#000}.btn-microsoft{border-color:#00a4ef}.btn-github{border-color:#333}.oauth-icon{font-size:18px}.switch-method{margin-top:12px;text-align:center;font-size:14px}.switch-method a{color:#4285f4;text-decoration:none}.switch-method a:hover{text-decoration:underline}.error-message{margin-top:16px;padding:12px;background:#fee;color:#c33;border-radius:4px;font-size:14px}.success-message{margin-top:16px;padding:12px;background:#efe;color:#3a3;border-radius:4px;font-size:14px}.loading-overlay{position:absolute;inset:0;background:#fffffff2;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #4285f4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.loading-text{margin:0;font-size:14px;color:#666}.login-link{margin-top:16px;text-align:center;font-size:14px;color:#666}.login-link a{color:#4285f4;text-decoration:none}.login-link a:hover{text-decoration:underline}\n"] }]
3516
- }], ctorParameters: () => [{ type: AuthService }], propDecorators: { title: [{
3995
+ }], ctorParameters: () => [{ type: AuthService }, { type: ProviderRegistryService }], propDecorators: { title: [{
3517
3996
  type: Input
3518
3997
  }], providers: [{
3519
3998
  type: Input
@@ -3779,5 +4258,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
3779
4258
  * Generated bundle index. Do not edit.
3780
4259
  */
3781
4260
 
3782
- export { ApiConnectionService, ApiResponse, AuthPageComponent, AuthService, CsrfService, DbService, LoginDialogComponent, MyEnvironmentModel, NgxStoneScriptPhpClientModule, RegisterComponent, SigninStatusService, TenantLoginComponent, TenantLoginDialogComponent, TenantRegisterComponent, TenantRegisterDialogComponent, TokenService, VerifyStatus };
4261
+ export { ApiConnectionService, ApiResponse, AuthPageComponent, AuthService, CsrfService, DbService, FilesService, LoginDialogComponent, MyEnvironmentModel, NgxStoneScriptPhpClientModule, ProviderRegistryService, RegisterComponent, SigninStatusService, TenantLoginComponent, TenantLoginDialogComponent, TenantRegisterComponent, TenantRegisterDialogComponent, TokenService, VerifyStatus };
3783
4262
  //# sourceMappingURL=progalaxyelabs-ngx-stonescriptphp-client.mjs.map