From 1198f8d6c439a2fe19292c599e24d10431bb5e0c Mon Sep 17 00:00:00 2001 From: yash-pouranik Date: Sat, 25 Jul 2026 17:35:55 +0530 Subject: [PATCH 1/2] feat: redesign integrations page with Appwrite-style provider grid and settings modal --- .../Settings/IntegrationsSettings.jsx | 709 ++++++++++-------- apps/web-dashboard/src/pages/Auth.jsx | 87 +-- 2 files changed, 405 insertions(+), 391 deletions(-) diff --git a/apps/web-dashboard/src/components/Settings/IntegrationsSettings.jsx b/apps/web-dashboard/src/components/Settings/IntegrationsSettings.jsx index 3b5508b3b..e1eb59693 100644 --- a/apps/web-dashboard/src/components/Settings/IntegrationsSettings.jsx +++ b/apps/web-dashboard/src/components/Settings/IntegrationsSettings.jsx @@ -1,6 +1,7 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useMemo } from 'react'; import { - Database, Shield, Mail, HardDrive, Check, Copy, Server + Shield, Database, Mail, HardDrive, Check, Copy, Search, X, Eye, EyeOff, + ExternalLink, CheckCircle, Info, Lock } from 'lucide-react'; import api from '../../utils/api'; import toast from 'react-hot-toast'; @@ -11,31 +12,57 @@ import DatabaseConfigForm from './DatabaseConfigForm'; import StorageConfigForm from './StorageConfigForm'; import MailTemplatesForm from './MailTemplatesForm'; -function IntegrationSectionCard({ title, description, icon: Icon, iconColor = 'var(--color-primary)', accentColor, children, style = {} }) { - return ( -
- {accentColor && ( -
- )} -
- {title && ( -
-
- {Icon && } -

{title}

-
- {description && ( -

- {description} -

- )} -
- )} - {children} -
-
- ); -} +/* ─── SVG Brand Icons ─── */ +const Icons = { + GitHub: () => ( + + + + ), + Google: () => ( + + + + + + + ), + Apple: () => ( + + + + ), + Discord: () => ( + + + + ), + MongoDB: () => ( + + + + ), + Redis: () => ( + + + + ), + Resend: () => ( + + + + ), + Supabase: () => ( + + + + ), + AWS: () => ( + + + + ) +}; export default function IntegrationsSettings({ project, @@ -51,6 +78,7 @@ export default function IntegrationsSettings({ handleResendKeySave, }) { const isViewer = role === 'viewer'; + const siteUrl = project?.siteUrl || ''; // Auth Providers State const [authProviders, setAuthProviders] = useState({ @@ -58,10 +86,14 @@ export default function IntegrationsSettings({ google: { enabled: false, clientId: '', clientSecret: '', hasClientSecret: false } }); const [isSavingProviders, setIsSavingProviders] = useState(false); - const [activeAuthTab, setActiveAuthTab] = useState('github'); - const [copiedUrl, setCopiedUrl] = useState(null); + const [selectedProviderModal, setSelectedProviderModal] = useState(null); // 'github' | 'google' | 'mongodb' | 'resend' | 'storage' + const [searchTerm, setSearchTerm] = useState(''); + + // Modal Form States for Active Social Provider + const [modalForm, setModalForm] = useState({ enabled: false, clientId: '', clientSecret: '' }); + const [showSecret, setShowSecret] = useState(false); + const [copiedUrl, setCopiedUrl] = useState(false); - const siteUrl = project?.siteUrl || ''; const githubCallbackUrl = `${PUBLIC_API_URL}/api/userAuth/social/github/callback`; const googleCallbackUrl = `${PUBLIC_API_URL}/api/userAuth/social/google/callback`; @@ -72,344 +104,228 @@ export default function IntegrationsSettings({ } }, [project?.authProviders]); - const handleProviderFieldChange = (provider, field, value) => { - setAuthProviders((prev) => ({ - ...prev, - [provider]: { ...prev[provider], [field]: value } - })); + const openOAuthModal = (providerKey) => { + const prov = authProviders[providerKey] || { enabled: false, clientId: '', clientSecret: '' }; + setModalForm({ + enabled: !!prov.enabled, + clientId: prov.clientId || '', + clientSecret: '' + }); + setShowSecret(false); + setCopiedUrl(false); + setSelectedProviderModal(providerKey); }; - const handleSaveProviders = async () => { + const handleSaveOAuthModal = async () => { + if (!selectedProviderModal) return; setIsSavingProviders(true); try { + const updatedProviderData = { + enabled: !!modalForm.enabled, + clientId: modalForm.clientId, + ...(modalForm.clientSecret ? { clientSecret: modalForm.clientSecret } : {}) + }; + const payload = { - github: { + github: selectedProviderModal === 'github' ? updatedProviderData : { enabled: !!authProviders.github.enabled, clientId: authProviders.github.clientId, - ...(authProviders.github.clientSecret ? { clientSecret: authProviders.github.clientSecret } : {}) }, - google: { + google: selectedProviderModal === 'google' ? updatedProviderData : { enabled: !!authProviders.google.enabled, clientId: authProviders.google.clientId, - ...(authProviders.google.clientSecret ? { clientSecret: authProviders.google.clientSecret } : {}) } }; + const res = await api.patch(`/api/projects/${projectId}/auth/providers`, payload); setAuthProviders(res.data.authProviders); if (onProjectUpdate) { onProjectUpdate(prev => ({ ...prev, authProviders: res.data.authProviders })); } - toast.success('Auth provider settings saved!'); + toast.success(`${selectedProviderModal === 'github' ? 'GitHub' : 'Google'} OAuth settings updated!`); + setSelectedProviderModal(null); } catch (err) { - toast.error(err.response?.data?.message || 'Failed to save auth provider settings'); + toast.error(err.response?.data?.message || 'Failed to update provider settings'); } finally { setIsSavingProviders(false); } }; - const copyToClipboard = async (text, type) => { + const copyToClipboard = async (text) => { if (!navigator?.clipboard) { toast.error('Clipboard access is not available.'); return; } try { await navigator.clipboard.writeText(text); - setCopiedUrl(type); - toast.success('Callback URL copied!'); - setTimeout(() => setCopiedUrl(null), 2000); + setCopiedUrl(true); + toast.success('OAuth Callback URL copied!'); + setTimeout(() => setCopiedUrl(false), 2000); } catch { toast.error('Failed to copy to clipboard.'); } }; + // Filter providers based on search input + const oauthProvidersList = useMemo(() => { + const list = [ + { id: 'github', name: 'GitHub', icon: Icons.GitHub, enabled: authProviders.github.enabled, configured: authProviders.github.hasClientSecret }, + { id: 'google', name: 'Google', icon: Icons.Google, enabled: authProviders.google.enabled, configured: authProviders.google.hasClientSecret }, + { id: 'apple', name: 'Apple', icon: Icons.Apple, disabled: true, statusText: 'Coming Soon' }, + { id: 'discord', name: 'Discord', icon: Icons.Discord, disabled: true, statusText: 'Coming Soon' } + ]; + + if (!searchTerm.trim()) return list; + return list.filter(p => p.name.toLowerCase().includes(searchTerm.toLowerCase())); + }, [authProviders, searchTerm]); + + const activeCallbackUrl = selectedProviderModal === 'google' ? googleCallbackUrl : githubCallbackUrl; + return ( -
+
- {/* 1. DATABASES SECTION */} - -
- {/* Primary MongoDB */} - - - {/* Redis (Coming Soon) */} -
-
-
- -
-

Redis Cache

-

- High performance memory cache & pub/sub engine. -

-
-
- - Coming Soon - -
+ {/* 1. OAUTH2 PROVIDERS GRID (APPWRITE STYLE) */} +
+
+
+

OAuth2 Providers

+

+ Enable third-party social logins for your application users. +

+
+ + {/* Provider Search Filter */} +
+ + setSearchTerm(e.target.value)} + style={{ ...inputStyle, paddingLeft: '30px', height: '32px', fontSize: '0.75rem' }} + />
- - - {/* 2. AUTH INTEGRATIONS SECTION */} - -
- - {/* Site URL warning check */} - {!siteUrl && ( -
- ⚠️ Site URL is not set: Please set your app's frontend URL in the General Settings so OAuth redirects to /auth/callback correctly. -
- )} - - {/* Auth Provider Selector Tabs */} -
- {[ - { id: 'github', label: 'GitHub OAuth', enabled: authProviders.github.enabled, configured: authProviders.github.hasClientSecret }, - { id: 'google', label: 'Google OAuth', enabled: authProviders.google.enabled, configured: authProviders.google.hasClientSecret } - ].map((prov) => ( -
+ )} + + {/* Provider Grid Cards */} +
+ {oauthProvidersList.map((prov) => { + const ProviderIcon = prov.icon; + const isConfigured = prov.configured || prov.enabled; + return ( +
setActiveAuthTab(prov.id)} + onClick={() => !prov.disabled && openOAuthModal(prov.id)} style={{ - padding: '8px 14px', - background: 'transparent', - border: 'none', - borderBottom: activeAuthTab === prov.id ? '2px solid var(--color-primary)' : '2px solid transparent', - color: activeAuthTab === prov.id ? '#fff' : 'var(--color-text-muted)', - fontSize: '0.8rem', - fontWeight: activeAuthTab === prov.id ? 600 : 400, - cursor: 'pointer', + background: 'rgba(255,255,255,0.02)', + border: '1px solid var(--color-border)', + borderRadius: '12px', + padding: '1.25rem', + cursor: prov.disabled ? 'not-allowed' : 'pointer', display: 'flex', - alignItems: 'center', - gap: '6px' + flexDirection: 'column', + justify: 'space-between', + minHeight: '110px', + opacity: prov.disabled ? 0.6 : 1, + transition: 'all 0.2s ease', }} + className={!prov.disabled ? "provider-card-hover" : ""} > - {prov.label} - {prov.enabled && ( - - )} - - ))} -
- - {/* Active Provider Form */} - {activeAuthTab === 'github' && ( -
-
-
-

GitHub OAuth Integration

-

- Allow users to log in with their GitHub account. -

+
+
+ +
+ {prov.name}
- -
- -
- - +
+ + {prov.statusText || (prov.enabled ? 'enabled' : isConfigured ? 'configured' : 'disabled')} +
- - -
- - handleProviderFieldChange('github', 'clientId', e.target.value)} - style={{ ...inputStyle, fontFamily: 'monospace' }} - disabled={isViewer} - /> - - - - Client Secret{' '} - - • {authProviders.github.hasClientSecret ? 'Configured (encrypted)' : 'Not set'} - - - } - > - handleProviderFieldChange('github', 'clientSecret', e.target.value)} - style={{ ...inputStyle, fontFamily: 'monospace' }} - disabled={isViewer} - /> -
-
- )} + ); + })} +
+
- {activeAuthTab === 'google' && ( -
-
-
-

Google OAuth Integration

-

- Allow users to log in with Google accounts. -

-
- + {/* 2. DATABASES SECTION */} +
+

Databases

+

+ Primary database clusters & caching layers. +

+
+
+
+
+
- - -
- - -
-
- -
- - handleProviderFieldChange('google', 'clientId', e.target.value)} - style={{ ...inputStyle, fontFamily: 'monospace' }} - disabled={isViewer} - /> - - - - Client Secret{' '} - - • {authProviders.google.hasClientSecret ? 'Configured (encrypted)' : 'Not set'} - - - } - > - handleProviderFieldChange('google', 'clientSecret', e.target.value)} - style={{ ...inputStyle, fontFamily: 'monospace' }} - disabled={isViewer} - /> - +
+

MongoDB

+ + {project?.resources?.db?.isExternal ? 'External Connected' : 'Default Managed'} +
- )} + +
- {!isViewer && ( -
- +
+
+
+ +
+
+

Redis Cache

+ Coming Soon +
- )} +

+ In-memory caching and message queues for real-time applications. +

+
- - - {/* 3. MAIL INTEGRATIONS SECTION */} - +
+ + {/* 3. MAIL SECTION */} +
+

Mail Delivery & Templates

+

+ Transactional email providers and dynamic template engine. +

- {/* Provider Selection Info */} -
-
- Active Provider: Resend.com (BYOK) - - SendGrid, AWS SES & urMail Engine support are in development. - +
+
+
+
+ +
+
+

Resend.com (BYOK API Key)

+ + {hasResendKey ? '• Configured (Encrypted at rest)' : '• Not configured'} + +
+
-
- {/* Resend BYOK Card */} -
-

Resend.com API Key

-

- Upload a per-project Resend API key to send mail from your custom domain. -

- - Resend API Key{' '} - - · {hasResendKey ? 'Configured' : 'Not configured'} - - - }> + - Blank defaults to onboarding@resend.dev}> + Defaults to onboarding@resend.dev}>
{!isViewer && ( -
+
@@ -446,22 +362,165 @@ export default function IntegrationsSettings({ )}
- {/* Mail Templates Sub-form */}
- - - {/* 4. STORAGE INTEGRATIONS SECTION */} - +
+ + {/* 4. STORAGE ENGINES */} +
+

Storage Engines (BYOS)

+

+ Connect external Supabase, AWS S3, or Cloudflare R2 object storage. +

- +
+ + {/* ─── APPWRITE STYLE OAUTH2 PROVIDER SETTINGS MODAL ─── */} + {selectedProviderModal && ( +
setSelectedProviderModal(null)} + > +
e.stopPropagation()} + > + {/* Close button */} + + + {/* Modal Header */} +
+
+ {selectedProviderModal === 'github' ? : } +
+
+

+ {selectedProviderModal === 'github' ? 'GitHub' : 'Google'} OAuth2 settings +

+
+
+

+ To use {selectedProviderModal === 'github' ? 'GitHub' : 'Google'} authentication in your application, fill in this form. +

+ +
+ + {/* Enable Toggle Switch */} +
+ Enable Provider + +
+ + {/* Client ID */} + + setModalForm(p => ({ ...p, clientId: e.target.value }))} + style={{ ...inputStyle, fontFamily: 'monospace' }} + disabled={isViewer} + /> + + + {/* Client Secret */} + +
+ setModalForm(p => ({ ...p, clientSecret: e.target.value }))} + style={{ ...inputStyle, fontFamily: 'monospace', paddingRight: '36px' }} + disabled={isViewer} + /> + +
+
+ + {/* Callout Box for Redirect URI */} +
+
+ + To complete set up, add this OAuth2 redirect URI to your {selectedProviderModal === 'github' ? 'GitHub app' : 'Google Cloud Console'} configuration. +
+ +
+ + +
+
+
+ + {/* Modal Action Buttons */} +
+ + +
+
+
+ )} + +
); } diff --git a/apps/web-dashboard/src/pages/Auth.jsx b/apps/web-dashboard/src/pages/Auth.jsx index d1555a3dc..a1f1bd9e0 100644 --- a/apps/web-dashboard/src/pages/Auth.jsx +++ b/apps/web-dashboard/src/pages/Auth.jsx @@ -2,12 +2,10 @@ import { useState, useEffect, useRef } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import api from '../utils/api'; import toast from 'react-hot-toast'; -import { Shield, X, AlertCircle, Monitor, LogOut, Key } from 'lucide-react'; +import { Shield, X, AlertCircle, Monitor, LogOut, Key, ExternalLink } from 'lucide-react'; import { useAuth } from '../context/AuthContext'; import AuthHeader from '../components/Auth/AuthHeader'; -import SocialAuthConfig from '../components/Auth/SocialAuthConfig'; -import SocialAuthModal from '../components/Auth/SocialAuthModal'; import UserTable from '../components/Auth/UserTable'; import Pagination from '../components/Database/Pagination'; import SectionHeader from '../components/Dashboard/SectionHeader'; @@ -52,17 +50,10 @@ export default function Auth() { const [project, setProject] = useState(null); const [isEnabling, setIsEnabling] = useState(false); const [isTogglingSignup, setIsTogglingSignup] = useState(false); - const [isSavingProviders, setIsSavingProviders] = useState(false); - const [isSocialAuthModalOpen, setIsSocialAuthModalOpen] = useState(false); const [isAddModalOpen, setIsAddModalOpen] = useState(false); const [isEditUsersSchemaModalOpen, setIsEditUsersSchemaModalOpen] = useState(false); const [editingUser, setEditingUser] = useState(null); // user being edited const latestUsersRequestId = useRef(0); - const [selectedProvider, setSelectedProvider] = useState('github'); - const [authProviders, setAuthProviders] = useState({ - github: { enabled: false, clientId: '', clientSecret: '', hasClientSecret: false }, - google: { enabled: false, clientId: '', clientSecret: '', hasClientSecret: false } - }); const [isSessionsModalOpen, setIsSessionsModalOpen] = useState(false); const [sessionsTargetUser, setSessionsTargetUser] = useState(null); @@ -74,11 +65,6 @@ export default function Auth() { const [newPassword, setNewPassword] = useState(''); const [isResetLoading, setIsResetLoading] = useState(false); - // Config - const siteUrl = project?.siteUrl || ''; - const githubCallbackUrl = `${PUBLIC_API_URL}/api/userAuth/social/github/callback`; - const googleCallbackUrl = `${PUBLIC_API_URL}/api/userAuth/social/google/callback`; - const myMember = project?.members?.find(m => { const memberId = typeof m.user === 'object' ? m.user?._id : m.user; return memberId?.toString() === user?._id?.toString() || m.email === user?.email; @@ -113,7 +99,6 @@ export default function Auth() { if (isMounted) { setProject(projectResult.data); - if (projectResult.data?.authProviders) setAuthProviders(projectResult.data.authProviders); if (projectResult.data?.isAuthEnabled) { const requestId = ++latestUsersRequestId.current; const usersRes = await api.get( @@ -158,36 +143,6 @@ export default function Auth() { } finally { setIsEnabling(false); } }; - const handleProviderFieldChange = (provider, field, value) => { - setAuthProviders((prev) => ({ - ...prev, - [provider]: { ...prev[provider], [field]: value } - })); - }; - - const handleSaveProviders = async () => { - setIsSavingProviders(true); - try { - const payload = { - github: { - enabled: !!authProviders.github.enabled, - clientId: authProviders.github.clientId, - ...(authProviders.github.clientSecret ? { clientSecret: authProviders.github.clientSecret } : {}) - }, - google: { - enabled: !!authProviders.google.enabled, - clientId: authProviders.google.clientId, - ...(authProviders.google.clientSecret ? { clientSecret: authProviders.google.clientSecret } : {}) - } - }; - const res = await api.patch(`/api/projects/${projectId}/auth/providers`, payload); - setAuthProviders(res.data.authProviders); - toast.success('Social auth saved'); - setIsSocialAuthModalOpen(false); - } catch { toast.error('Failed to save settings'); } - finally { setIsSavingProviders(false); } - }; - const handleTogglePublicSignup = async (enable) => { if (isTogglingSignup || isViewer) return; setIsTogglingSignup(true); @@ -306,21 +261,6 @@ export default function Auth() { onAddUser={isViewer ? null : () => setIsAddModalOpen(true)} /> - {/* Social Auth Modal */} - setIsSocialAuthModalOpen(false)} - selectedProvider={selectedProvider} - setSelectedProvider={setSelectedProvider} - authProviders={authProviders} - handleProviderFieldChange={handleProviderFieldChange} - handleSaveProviders={handleSaveProviders} - isSavingProviders={isSavingProviders} - siteUrl={siteUrl} - githubCallbackUrl={githubCallbackUrl} - googleCallbackUrl={googleCallbackUrl} - /> - {/* Add / Edit User Drawer */}
- setIsSocialAuthModalOpen(true)} - setSelectedProvider={setSelectedProvider} - /> +
+
+
+

+ OAuth2 & Social Providers +

+

+ Configure GitHub, Google, and third-party login integrations in Project Settings. +

+
+ +
+

Security Policy

From a17e56c9f4d87b198d8be4f247835ed8b20fd7c1 Mon Sep 17 00:00:00 2001 From: yash-pouranik Date: Sat, 25 Jul 2026 18:06:20 +0530 Subject: [PATCH 2/2] fix: address code review comments for integrations page accessibility, normalization, validation, and layout --- .../Settings/IntegrationsSettings.jsx | 136 +++++++++--------- apps/web-dashboard/src/pages/Auth.jsx | 2 +- 2 files changed, 70 insertions(+), 68 deletions(-) diff --git a/apps/web-dashboard/src/components/Settings/IntegrationsSettings.jsx b/apps/web-dashboard/src/components/Settings/IntegrationsSettings.jsx index e1eb59693..1366b1a36 100644 --- a/apps/web-dashboard/src/components/Settings/IntegrationsSettings.jsx +++ b/apps/web-dashboard/src/components/Settings/IntegrationsSettings.jsx @@ -1,7 +1,7 @@ -import React, { useState, useEffect, useMemo } from 'react'; +import React, { useState, useEffect, useMemo, useRef } from 'react'; import { Shield, Database, Mail, HardDrive, Check, Copy, Search, X, Eye, EyeOff, - ExternalLink, CheckCircle, Info, Lock + CheckCircle, Info, Server } from 'lucide-react'; import api from '../../utils/api'; import toast from 'react-hot-toast'; @@ -51,19 +51,24 @@ const Icons = { - ), - Supabase: () => ( - - - - ), - AWS: () => ( - - - ) }; +const normalizeAuthProviders = (raw) => ({ + github: { + enabled: !!raw?.github?.enabled, + clientId: raw?.github?.clientId || '', + clientSecret: raw?.github?.clientSecret || '', + hasClientSecret: !!raw?.github?.hasClientSecret + }, + google: { + enabled: !!raw?.google?.enabled, + clientId: raw?.google?.clientId || '', + clientSecret: raw?.google?.clientSecret || '', + hasClientSecret: !!raw?.google?.hasClientSecret + } +}); + export default function IntegrationsSettings({ project, projectId, @@ -81,29 +86,39 @@ export default function IntegrationsSettings({ const siteUrl = project?.siteUrl || ''; // Auth Providers State - const [authProviders, setAuthProviders] = useState({ - github: { enabled: false, clientId: '', clientSecret: '', hasClientSecret: false }, - google: { enabled: false, clientId: '', clientSecret: '', hasClientSecret: false } - }); + const [authProviders, setAuthProviders] = useState(() => normalizeAuthProviders(project?.authProviders)); const [isSavingProviders, setIsSavingProviders] = useState(false); - const [selectedProviderModal, setSelectedProviderModal] = useState(null); // 'github' | 'google' | 'mongodb' | 'resend' | 'storage' + const [selectedProviderModal, setSelectedProviderModal] = useState(null); const [searchTerm, setSearchTerm] = useState(''); - // Modal Form States for Active Social Provider + // Modal Form States const [modalForm, setModalForm] = useState({ enabled: false, clientId: '', clientSecret: '' }); const [showSecret, setShowSecret] = useState(false); const [copiedUrl, setCopiedUrl] = useState(false); + const closeButtonRef = useRef(null); + const githubCallbackUrl = `${PUBLIC_API_URL}/api/userAuth/social/github/callback`; const googleCallbackUrl = `${PUBLIC_API_URL}/api/userAuth/social/google/callback`; useEffect(() => { if (project?.authProviders) { - const providers = project.authProviders; - Promise.resolve().then(() => setAuthProviders(providers)); + const normalized = normalizeAuthProviders(project.authProviders); + Promise.resolve().then(() => setAuthProviders(normalized)); } }, [project?.authProviders]); + useEffect(() => { + if (!selectedProviderModal) return; + closeButtonRef.current?.focus(); + + const handleKeyDown = (e) => { + if (e.key === 'Escape') setSelectedProviderModal(null); + }; + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [selectedProviderModal]); + const openOAuthModal = (providerKey) => { const prov = authProviders[providerKey] || { enabled: false, clientId: '', clientSecret: '' }; setModalForm({ @@ -118,6 +133,20 @@ export default function IntegrationsSettings({ const handleSaveOAuthModal = async () => { if (!selectedProviderModal) return; + + // Validation: when enabling, clientId and clientSecret (new or existing) are required + if (modalForm.enabled) { + if (!modalForm.clientId.trim()) { + toast.error("Client ID is required when enabling a provider."); + return; + } + const existingHasSecret = authProviders[selectedProviderModal]?.hasClientSecret; + if (!modalForm.clientSecret.trim() && !existingHasSecret) { + toast.error("Client Secret is required when enabling a provider."); + return; + } + } + setIsSavingProviders(true); try { const updatedProviderData = { @@ -138,9 +167,10 @@ export default function IntegrationsSettings({ }; const res = await api.patch(`/api/projects/${projectId}/auth/providers`, payload); - setAuthProviders(res.data.authProviders); + const normalized = normalizeAuthProviders(res.data.authProviders); + setAuthProviders(normalized); if (onProjectUpdate) { - onProjectUpdate(prev => ({ ...prev, authProviders: res.data.authProviders })); + onProjectUpdate(prev => ({ ...prev, authProviders: normalized })); } toast.success(`${selectedProviderModal === 'github' ? 'GitHub' : 'Google'} OAuth settings updated!`); setSelectedProviderModal(null); @@ -166,7 +196,6 @@ export default function IntegrationsSettings({ } }; - // Filter providers based on search input const oauthProvidersList = useMemo(() => { const list = [ { id: 'github', name: 'GitHub', icon: Icons.GitHub, enabled: authProviders.github.enabled, configured: authProviders.github.hasClientSecret }, @@ -184,7 +213,7 @@ export default function IntegrationsSettings({ return (
- {/* 1. OAUTH2 PROVIDERS GRID (APPWRITE STYLE) */} + {/* 1. OAUTH2 PROVIDERS GRID */}
@@ -194,7 +223,6 @@ export default function IntegrationsSettings({

- {/* Provider Search Filter */}
)} - {/* Provider Grid Cards */}
{oauthProvidersList.map((prov) => { const ProviderIcon = prov.icon; @@ -221,7 +248,16 @@ export default function IntegrationsSettings({ return (
!prov.disabled && openOAuthModal(prov.id)} + onKeyDown={(e) => { + if (!prov.disabled && (e.key === 'Enter' || e.key === ' ')) { + e.preventDefault(); + openOAuthModal(prov.id); + } + }} style={{ background: 'rgba(255,255,255,0.02)', border: '1px solid var(--color-border)', @@ -230,7 +266,7 @@ export default function IntegrationsSettings({ cursor: prov.disabled ? 'not-allowed' : 'pointer', display: 'flex', flexDirection: 'column', - justify: 'space-between', + justifyContent: 'space-between', minHeight: '110px', opacity: prov.disabled ? 0.6 : 1, transition: 'all 0.2s ease', @@ -265,41 +301,7 @@ export default function IntegrationsSettings({ {/* 2. DATABASES SECTION */}
-

Databases

-

- Primary database clusters & caching layers. -

-
-
-
-
- -
-
-

MongoDB

- - {project?.resources?.db?.isExternal ? 'External Connected' : 'Default Managed'} - -
-
- -
- -
-
-
- -
-
-

Redis Cache

- Coming Soon -
-
-

- In-memory caching and message queues for real-time applications. -

-
-
+
{/* 3. MAIL SECTION */} @@ -368,16 +370,15 @@ export default function IntegrationsSettings({ {/* 4. STORAGE ENGINES */}
-

Storage Engines (BYOS)

-

- Connect external Supabase, AWS S3, or Cloudflare R2 object storage. -

{/* ─── APPWRITE STYLE OAUTH2 PROVIDER SETTINGS MODAL ─── */} {selectedProviderModal && (
setSelectedProviderModal(null)} @@ -389,6 +390,7 @@ export default function IntegrationsSettings({ > {/* Close button */}
-

+

{selectedProviderModal === 'github' ? 'GitHub' : 'Google'} OAuth2 settings

diff --git a/apps/web-dashboard/src/pages/Auth.jsx b/apps/web-dashboard/src/pages/Auth.jsx index a1f1bd9e0..0827c1d11 100644 --- a/apps/web-dashboard/src/pages/Auth.jsx +++ b/apps/web-dashboard/src/pages/Auth.jsx @@ -411,7 +411,7 @@ export default function Auth() { OAuth2 & Social Providers

- Configure GitHub, Google, and third-party login integrations in Project Settings. + Configure GitHub, Google, and third-party login integrations in Settings → Integrations & Services.