@@ -60,9 +60,9 @@ export interface PluginSettingsPageData {
6060
6161export function renderPluginSettingsPage ( data : PluginSettingsPageData ) : string {
6262 const { plugin, activity = [ ] , user } = data
63- // true only when the plugin has at least one user-configurable setting key
64- // (_-prefixed keys are internal metadata, not settings the user can edit)
65- const hasUserSettings = Object . keys ( plugin . settings || { } ) . some ( k => ! k . startsWith ( '_' ) )
63+ // true when plugin has configurable settings or a custom renderer (which handles empty state itself)
64+ const hasUserSettings = plugin . id in pluginSettingsComponents ||
65+ Object . keys ( plugin . settings || { } ) . some ( k => ! k . startsWith ( '_' ) )
6666 const defaultTab = hasUserSettings ? 'settings' : 'info'
6767
6868 const pageContent = `
@@ -768,6 +768,7 @@ type PluginSettingsRenderer = (plugin: any, settings: PluginSettings) => string
768768const pluginSettingsComponents : Record < string , PluginSettingsRenderer > = {
769769 'otp-login' : renderOTPLoginSettingsContent ,
770770 'email' : renderEmailSettingsContent ,
771+ 'oauth-providers' : renderOAuthProvidersSettingsContent ,
771772}
772773
773774/**
@@ -1521,6 +1522,108 @@ defineUserProfile({
15211522 `
15221523}
15231524
1525+ /**
1526+ * OAuth Providers plugin settings content
1527+ */
1528+ function renderOAuthProvidersSettingsContent ( _plugin : any , settings : PluginSettings ) : string {
1529+ const providers = ( settings . providers as any ) || { }
1530+ const github = providers . github || { }
1531+ const google = providers . google || { }
1532+
1533+ const inputClass = 'w-full px-3 py-2 rounded-lg bg-white/5 border border-white/10 focus:border-blue-500 focus:outline-none text-white'
1534+
1535+ function renderProviderCard ( name : string , label : string , icon : string , data : any ) : string {
1536+ const clientId = escapeHtmlAttr ( data . clientId || '' )
1537+ const clientSecret = escapeHtmlAttr ( data . clientSecret || '' )
1538+ const enabled = data . enabled === true
1539+
1540+ return `
1541+ <div class="backdrop-blur-md bg-white/5 rounded-xl border border-white/10 p-6">
1542+ <div class="flex items-center justify-between mb-4">
1543+ <h3 class="text-lg font-semibold text-white flex items-center gap-2">
1544+ <span>${ icon } </span> ${ label }
1545+ </h3>
1546+ <label class="relative inline-flex items-center cursor-pointer">
1547+ <input type="checkbox" id="oauth_${ name } _enabled" ${ enabled ? 'checked' : '' } class="sr-only peer">
1548+ <div class="w-11 h-6 bg-gray-600 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-800 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600"></div>
1549+ <span class="ml-2 text-sm text-gray-300">Enabled</span>
1550+ </label>
1551+ </div>
1552+ <div class="space-y-4">
1553+ <div>
1554+ <label for="oauth_${ name } _clientId" class="block text-sm font-medium text-gray-300 mb-2">Client ID</label>
1555+ <input type="text" id="oauth_${ name } _clientId" value="${ clientId } " placeholder="${ label } OAuth Client ID" class="${ inputClass } ">
1556+ </div>
1557+ <div>
1558+ <label for="oauth_${ name } _clientSecret" class="block text-sm font-medium text-gray-300 mb-2">Client Secret</label>
1559+ <input type="password" id="oauth_${ name } _clientSecret" value="${ clientSecret } " placeholder="${ label } OAuth Client Secret" class="${ inputClass } ">
1560+ </div>
1561+ </div>
1562+ </div>
1563+ `
1564+ }
1565+
1566+ return `
1567+ <div class="space-y-6">
1568+ <div class="bg-blue-500/10 border border-blue-500/20 rounded-lg p-4">
1569+ <p class="text-blue-200 text-sm">
1570+ Configure OAuth provider credentials below. Credentials are stored securely and used server-side only.
1571+ Make sure to add the callback URL <code class="text-blue-300">/auth/oauth/[provider]/callback</code> to your OAuth app's allowed redirect URIs.
1572+ </p>
1573+ </div>
1574+
1575+ ${ renderProviderCard ( 'github' , 'GitHub' , '🐙' , github ) }
1576+ ${ renderProviderCard ( 'google' , 'Google' , '🔵' , google ) }
1577+ </div>
1578+
1579+ <script>
1580+ (function () {
1581+ const origSave = window.saveSettings;
1582+ window.saveSettings = async function () {
1583+ const settings = {
1584+ providers: {
1585+ github: {
1586+ clientId: document.getElementById('oauth_github_clientId').value,
1587+ clientSecret: document.getElementById('oauth_github_clientSecret').value,
1588+ enabled: document.getElementById('oauth_github_enabled').checked,
1589+ },
1590+ google: {
1591+ clientId: document.getElementById('oauth_google_clientId').value,
1592+ clientSecret: document.getElementById('oauth_google_clientSecret').value,
1593+ enabled: document.getElementById('oauth_google_enabled').checked,
1594+ },
1595+ }
1596+ };
1597+
1598+ const saveButton = document.getElementById('save-button');
1599+ saveButton.disabled = true;
1600+ saveButton.textContent = 'Saving...';
1601+
1602+ try {
1603+ const response = await fetch('/admin/plugins/oauth-providers/settings', {
1604+ method: 'POST',
1605+ headers: { 'Content-Type': 'application/json' },
1606+ body: JSON.stringify(settings),
1607+ });
1608+ const result = await response.json();
1609+ if (result.success) {
1610+ showNotification('Settings saved successfully', 'success');
1611+ setTimeout(() => location.reload(), 1000);
1612+ } else {
1613+ throw new Error(result.error || 'Failed to save settings');
1614+ }
1615+ } catch (error) {
1616+ showNotification(error.message, 'error');
1617+ } finally {
1618+ saveButton.disabled = false;
1619+ saveButton.textContent = 'Save Settings';
1620+ }
1621+ };
1622+ })();
1623+ </script>
1624+ `
1625+ }
1626+
15241627/**
15251628 * Check if a plugin has a custom settings component
15261629 */
0 commit comments