Private
Public Access
1
0

feat: Fluent UI Outlook Lite + connections mockup

This commit is contained in:
2026-04-14 18:52:25 +00:00
parent 1199eff6c3
commit dfa4010406
34820 changed files with 1003813 additions and 205 deletions

View File

@@ -1,6 +1,249 @@
import { useState, useEffect, useCallback } from 'react';
import { outlookService } from './services/OutlookService';
import React, { useState, useCallback, useEffect } from 'react';
import {
Avatar,
Badge,
Button,
DialogBody,
DialogContent,
Dialog,
DialogSurface,
Input,
Textarea,
makeStyles,
tokens,
Spinner,
Divider,
Text,
Label,
} from '@fluentui/react-components';
import {
Mail24Regular,
Send24Regular,
ArrowSync24Regular,
Search24Regular,
Compose24Regular,
Archive24Regular,
MailInbox24Regular,
Mail24Filled,
} from '@fluentui/react-icons';
import type { Email, Folder } from './types';
import { OutlookService } from './services/OutlookService';
const outlookService = new OutlookService();
const useStyles = makeStyles({
appShell: {
display: 'flex',
height: '100vh',
width: '100vw',
overflow: 'hidden',
},
navRail: {
width: '64px',
backgroundColor: tokens.colorNeutralBackground2,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
paddingTop: '12px',
paddingBottom: '12px',
gap: '8px',
borderRight: `1px solid ${tokens.colorNeutralStroke2}`,
flexShrink: 0,
},
navIcon: {
width: '40px',
height: '40px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: '8px',
cursor: 'pointer',
color: tokens.colorNeutralForeground2,
transition: 'all 150ms ease',
border: 'none',
background: 'transparent',
fontSize: '0',
},
navIconActive: {
backgroundColor: tokens.colorBrandBackground,
color: tokens.colorNeutralForegroundInverted,
},
toolbar: {
display: 'flex',
alignItems: 'center',
gap: '8px',
padding: '8px 16px',
borderBottom: `1px solid ${tokens.colorNeutralStroke2}`,
backgroundColor: tokens.colorNeutralBackground2,
flexShrink: 0,
},
toolbarSpacer: {
flex: 1,
},
contentArea: {
flex: 1,
display: 'flex',
overflow: 'hidden',
},
folderPanel: {
width: '220px',
borderRight: `1px solid ${tokens.colorNeutralStroke2}`,
backgroundColor: tokens.colorNeutralBackground2,
overflowY: 'auto',
padding: '8px 0',
flexShrink: 0,
},
folderItem: {
display: 'flex',
alignItems: 'center',
gap: '10px',
padding: '8px 16px',
cursor: 'pointer',
fontSize: '14px',
color: tokens.colorNeutralForeground2,
transition: 'all 100ms',
border: 'none',
background: 'transparent',
width: '100%',
textAlign: 'left',
borderRadius: '0',
},
folderItemActive: {
backgroundColor: tokens.colorBrandBackground2,
color: tokens.colorBrandForeground1,
borderRight: `3px solid ${tokens.colorBrandForeground1}`,
},
folderBadge: {
marginLeft: 'auto',
},
emailListPanel: {
width: '340px',
borderRight: `1px solid ${tokens.colorNeutralStroke2}`,
overflowY: 'auto',
flexShrink: 0,
backgroundColor: tokens.colorNeutralBackground1,
},
emailItem: {
padding: '12px 16px',
borderBottom: `1px solid ${tokens.colorNeutralStroke2}`,
cursor: 'pointer',
transition: 'background 100ms',
backgroundColor: tokens.colorNeutralBackground1,
},
emailItemUnread: {
borderLeft: `3px solid ${tokens.colorBrandForeground1}`,
},
emailItemSelected: {
backgroundColor: tokens.colorBrandBackground2,
},
emailFrom: {
fontSize: '14px',
fontWeight: '600',
color: tokens.colorNeutralForeground1,
display: 'flex',
alignItems: 'center',
gap: '6px',
},
emailSubject: {
fontSize: '13px',
color: tokens.colorNeutralForeground1,
whiteSpace: 'nowrap' as const,
overflow: 'hidden',
textOverflow: 'ellipsis',
marginTop: '2px',
},
emailPreview: {
fontSize: '12px',
color: tokens.colorNeutralForeground3,
whiteSpace: 'nowrap' as const,
overflow: 'hidden',
textOverflow: 'ellipsis',
marginTop: '2px',
},
emailTime: {
fontSize: '11px',
color: tokens.colorNeutralForeground3,
marginLeft: 'auto',
flexShrink: 0,
},
detailPanel: {
flex: 1,
overflowY: 'auto',
padding: '24px 32px',
backgroundColor: tokens.colorNeutralBackground1,
},
detailHeader: {
display: 'flex',
gap: '14px',
alignItems: 'flex-start',
marginBottom: '20px',
},
detailMeta: {
flex: 1,
},
detailSubject: {
fontSize: '18px',
fontWeight: '700',
color: tokens.colorNeutralForeground1,
marginTop: '4px',
},
detailBody: {
fontSize: '14px',
lineHeight: '1.7',
color: tokens.colorNeutralForeground1,
whiteSpace: 'pre-wrap' as const,
marginTop: '16px',
},
detailActions: {
display: 'flex',
gap: '8px',
marginTop: '24px',
},
emptyState: {
flex: 1,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
gap: '12px',
color: tokens.colorNeutralForeground3,
backgroundColor: tokens.colorNeutralBackground1,
},
composeField: {
display: 'flex',
flexDirection: 'column',
gap: '4px',
padding: '8px 0',
},
composeFooter: {
display: 'flex',
gap: '8px',
paddingTop: '16px',
},
// Connections view
connectionsPanel: {
flex: 1,
overflowY: 'auto',
padding: '32px',
backgroundColor: tokens.colorNeutralBackground1,
},
connCard: {
backgroundColor: tokens.colorNeutralBackground2,
border: `1px solid ${tokens.colorNeutralStroke2}`,
borderRadius: '8px',
padding: '16px',
transition: 'all 150ms',
},
connCode: {
background: '#1a1a2e',
borderRadius: '6px',
padding: '16px 20px',
fontFamily: 'monospace',
fontSize: '13px',
color: '#a0a0b0',
lineHeight: 1.8,
},
});
const FOLDERS: Folder[] = [
{ id: 'inbox', name: 'Bandeja de entrada', icon: '📥', unreadCount: 4 },
@@ -22,25 +265,29 @@ function formatTime(iso: string): string {
}
function getInitials(name: string): string {
return name
.split(' ')
.map((n) => n[0])
.slice(0, 2)
.join('')
.toUpperCase();
return name.split(' ').map(n => n[0]).slice(0, 2).join('').toUpperCase();
}
function getAvatarColor(name: string): string {
const colors = ['#D83B01', '#A43751', '#498205', '#008272', '#005CC5', '#5C2D91', '#004078', '#B4009E'];
let hash = 0;
for (let i = 0; i < name.length; i++) hash = name.charCodeAt(i) + ((hash << 5) - hash);
return colors[Math.abs(hash) % colors.length];
}
export default function App() {
const styles = useStyles();
const [view, setView] = useState<'mail' | 'connections'>('mail');
const [activeFolder, setActiveFolder] = useState<string>('inbox');
const [emails, setEmails] = useState<Email[]>([]);
const [selectedEmail, setSelectedEmail] = useState<Email | null>(null);
const [loading, setLoading] = useState(false);
const [showCompose, setShowCompose] = useState(false);
const [search, setSearch] = useState('');
const [composeOpen, setComposeOpen] = useState(false);
const [composeTo, setComposeTo] = useState('');
const [composeSubject, setComposeSubject] = useState('');
const [composeBody, setComposeBody] = useState('');
const [sending, setSending] = useState(false);
const [search, setSearch] = useState('');
const loadEmails = useCallback(async () => {
setLoading(true);
@@ -54,16 +301,14 @@ export default function App() {
}, [activeFolder]);
useEffect(() => {
loadEmails();
}, [loadEmails]);
if (view === 'mail') loadEmails();
}, [view, activeFolder, loadEmails]);
const handleSelectEmail = async (email: Email) => {
setSelectedEmail(email);
if (!email.isRead) {
await outlookService.markAsRead(email.id);
setEmails((prev) =>
prev.map((e) => (e.id === email.id ? { ...e, isRead: true } : e))
);
setEmails(prev => prev.map(e => e.id === email.id ? { ...e, isRead: true } : e));
}
};
@@ -71,12 +316,8 @@ export default function App() {
if (!composeTo || !composeSubject) return;
setSending(true);
try {
await outlookService.sendMessage({
to: composeTo,
subject: composeSubject,
body: composeBody,
});
setShowCompose(false);
await outlookService.sendMessage({ to: composeTo, subject: composeSubject, body: composeBody });
setComposeOpen(false);
setComposeTo('');
setComposeSubject('');
setComposeBody('');
@@ -85,212 +326,325 @@ export default function App() {
}
};
const filteredEmails = emails.filter(
(e) =>
e.subject.toLowerCase().includes(search.toLowerCase()) ||
e.fromName.toLowerCase().includes(search.toLowerCase())
const filteredEmails = emails.filter(e =>
e.subject.toLowerCase().includes(search.toLowerCase()) ||
e.fromName.toLowerCase().includes(search.toLowerCase())
);
const activeFolderData = FOLDERS.find((f) => f.id === activeFolder)!;
const activeFolderData = FOLDERS.find(f => f.id === activeFolder)!;
return (
<div className="app">
{/* Sidebar */}
<div className="sidebar">
<div className="sidebar-icon" title="Outlook Lite">
📧
</div>
<div
className="sidebar-icon"
title="Buscar"
onClick={() => setSearch('')}
<div className={styles.appShell}>
{/* Navigation rail */}
<div className={styles.navRail}>
<button
className={`${styles.navIcon} ${view === 'mail' ? styles.navIconActive : ''}`}
onClick={() => setView('mail')}
title="Correo"
>
🔍
</div>
<div
className="sidebar-icon"
<Mail24Regular fontSize={20} />
</button>
<button
className={`${styles.navIcon} ${view === 'connections' ? styles.navIconActive : ''}`}
onClick={() => setView('connections')}
title="Conexiones"
>
<Archive24Regular fontSize={20} />
</button>
<div style={{ flex: 1 }} />
<button
className={styles.navIcon}
onClick={() => setComposeOpen(true)}
title="Redactar"
onClick={() => setShowCompose(true)}
>
</div>
<Compose24Regular fontSize={20} />
</button>
</div>
<div className="main-area">
{/* Main area */}
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
{/* Toolbar */}
<div className="toolbar">
<button
className="toolbar-btn primary"
onClick={() => setShowCompose(true)}
>
<div className={styles.toolbar}>
<Button appearance="primary" icon={<Compose24Regular />} onClick={() => setComposeOpen(true)}>
Redactar
</button>
<button className="toolbar-btn" onClick={loadEmails}>
🔄 Recargar
</button>
<div className="search-box">
<span>🔍</span>
<input
placeholder="Buscar en correos..."
</Button>
<Button appearance="subtle" icon={<ArrowSync24Regular />} onClick={loadEmails}>
Recargar
</Button>
<div style={{ flex: 1, position: 'relative' }}>
<Search24Regular style={{ position: 'absolute', left: 10, top: 10, color: tokens.colorNeutralForeground3, pointerEvents: 'none' }} />
<Input
placeholder="Buscar correos..."
value={search}
onChange={(e) => setSearch(e.target.value)}
onChange={(_, d) => setSearch(d.value)}
style={{ width: '100%', paddingLeft: '32px' }}
/>
</div>
</div>
{/* Content */}
<div className="content">
{/* Folder list */}
<div className="folder-list">
{FOLDERS.map((folder) => (
<div
key={folder.id}
className={`folder-item ${activeFolder === folder.id ? 'active' : ''}`}
onClick={() => setActiveFolder(folder.id)}
>
<span className="folder-icon">{folder.icon}</span>
<span>{folder.name}</span>
{folder.unreadCount > 0 && (
<span className="unread">{folder.unreadCount}</span>
<div className={styles.contentArea}>
{view === 'mail' ? (
<>
{/* Folder panel */}
<div className={styles.folderPanel}>
{FOLDERS.map(folder => (
<button
key={folder.id}
className={`${styles.folderItem} ${activeFolder === folder.id ? styles.folderItemActive : ''}`}
onClick={() => setActiveFolder(folder.id)}
>
<span style={{ fontSize: '16px' }}>{folder.id === 'inbox' ? <MailInbox24Regular fontSize={16} /> : folder.id === 'sent' ? <Send24Regular fontSize={16} /> : folder.id === 'drafts' ? <Compose24Regular fontSize={16} /> : '🗑️'}</span>
<span>{folder.name}</span>
{folder.unreadCount > 0 && (
<Badge className={styles.folderBadge} appearance="filled" color="brand" size="small">
{folder.unreadCount}
</Badge>
)}
</button>
))}
</div>
{/* Email list */}
<div className={styles.emailListPanel}>
{loading ? (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '40px' }}>
<Spinner />
</div>
) : filteredEmails.length === 0 ? (
<div className={styles.emptyState}>
<Mail24Regular fontSize={40} style={{ opacity: 0.4 }} />
<Text>No hay correos en {activeFolderData.name}</Text>
</div>
) : (
filteredEmails.map(email => (
<div
key={email.id}
className={`${styles.emailItem} ${!email.isRead ? styles.emailItemUnread : ''} ${selectedEmail?.id === email.id ? styles.emailItemSelected : ''}`}
onClick={() => handleSelectEmail(email)}
>
<div className={styles.emailFrom}>
<Avatar
name={email.fromName}
initials={getInitials(email.fromName)}
size={28}
badge={!email.isRead ? { status: 'available' } : undefined}
style={{ backgroundColor: getAvatarColor(email.fromName) }}
/>
<span>{email.fromName}</span>
<span className={styles.emailTime}>{formatTime(email.receivedAt)}</span>
</div>
<div className={styles.emailSubject}>{email.subject}</div>
<div className={styles.emailPreview}>{email.body.substring(0, 60)}</div>
</div>
))
)}
</div>
))}
</div>
{/* Email list */}
<div className="email-list">
{loading ? (
<div className="loading">
<div className="spinner" />
<span>Cargando...</span>
</div>
) : filteredEmails.length === 0 ? (
<div className="empty-state">
<div className="empty-state-icon">📭</div>
<div className="empty-state-text">No hay correos en {activeFolderData.name}</div>
</div>
) : (
filteredEmails.map((email) => (
<div
key={email.id}
className={`email-item ${!email.isRead ? 'unread' : ''} ${selectedEmail?.id === email.id ? 'selected' : ''}`}
onClick={() => handleSelectEmail(email)}
>
<div className="email-from">
{email.fromName}
<span className="email-time">{formatTime(email.receivedAt)}</span>
{/* Detail */}
{selectedEmail ? (
<div className={styles.detailPanel}>
<div className={styles.detailHeader}>
<Avatar
name={selectedEmail.fromName}
initials={getInitials(selectedEmail.fromName)}
size={40}
style={{ backgroundColor: getAvatarColor(selectedEmail.fromName) }}
/>
<div className={styles.detailMeta}>
<Text weight="semibold" size={300}>{selectedEmail.fromName}</Text>
<Text className={styles.detailSubject}>{selectedEmail.subject}</Text>
<Text size={200} style={{ color: tokens.colorNeutralForeground3, marginTop: '2px' }}>
{new Date(selectedEmail.receivedAt).toLocaleString('es-ES', {
day: 'numeric', month: 'long', year: 'numeric', hour: '2-digit', minute: '2-digit',
})}
</Text>
</div>
</div>
<div className="email-subject">{email.subject}</div>
<div className="email-preview">
{email.body.substring(0, 60)}
<Divider />
<div className={styles.detailBody}>{selectedEmail.body}</div>
<div className={styles.detailActions}>
<Button
appearance="primary"
icon={<Send24Regular />}
onClick={() => {
setComposeTo(selectedEmail.from);
setComposeSubject('Re: ' + selectedEmail.subject);
setComposeOpen(true);
}}
>
Responder
</Button>
<Button
appearance="subtle"
icon={<Send24Regular />}
onClick={() => {
setComposeTo(selectedEmail.from);
setComposeSubject(selectedEmail.subject);
setComposeOpen(true);
}}
>
Reenviar
</Button>
</div>
</div>
))
)}
</div>
{/* Email detail */}
{selectedEmail ? (
<div className="email-detail">
<div className="email-detail-header">
<div className="email-avatar">
{getInitials(selectedEmail.fromName)}
) : (
<div className={styles.emptyState}>
<Mail24Filled fontSize={48} style={{ opacity: 0.25, color: tokens.colorNeutralForeground3 }} />
<Text size={400} style={{ opacity: 0.5 }}>Selecciona un correo para leerlo</Text>
</div>
<div className="email-meta">
<div className="email-meta-from">{selectedEmail.fromName}</div>
<div className="email-meta-subject">{selectedEmail.subject}</div>
<div className="email-meta-time">
{new Date(selectedEmail.receivedAt).toLocaleString('es-ES', {
day: 'numeric',
month: 'long',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
})}
</div>
</div>
</div>
<div className="email-body">{selectedEmail.body}</div>
<div className="email-actions">
<button
className="toolbar-btn"
onClick={() => {
setComposeTo(selectedEmail.from);
setComposeSubject('Re: ' + selectedEmail.subject);
setShowCompose(true);
}}
>
Responder
</button>
<button
className="toolbar-btn"
onClick={() => {
setComposeTo(selectedEmail.from);
setComposeSubject(selectedEmail.subject);
setShowCompose(true);
}}
>
Reenviar
</button>
</div>
</div>
)}
</>
) : (
<div className="empty-state">
<div className="empty-state-icon">📧</div>
<div className="empty-state-text">Selecciona un correo para leerlo</div>
</div>
<ConnectionsMockup />
)}
</div>
</div>
{/* Compose modal */}
{showCompose && (
<div className="compose-overlay" onClick={() => setShowCompose(false)}>
<div className="compose" onClick={(e) => e.stopPropagation()}>
<div className="compose-header">
<span>Nuevo mensaje</span>
<button className="compose-close" onClick={() => setShowCompose(false)}>
</button>
</div>
<div className="compose-field">
<span className="compose-field-label">Para</span>
<input
placeholder="destinatario@ejemplo.com"
value={composeTo}
onChange={(e) => setComposeTo(e.target.value)}
/>
</div>
<div className="compose-field">
<span className="compose-field-label">Asunto</span>
<input
placeholder="Asunto del mensaje"
value={composeSubject}
onChange={(e) => setComposeSubject(e.target.value)}
/>
</div>
<div className="compose-body">
<textarea
placeholder="Escribe tu mensaje aquí..."
value={composeBody}
onChange={(e) => setComposeBody(e.target.value)}
/>
</div>
<div className="compose-footer">
<button
className="toolbar-btn primary"
onClick={handleSend}
disabled={sending || !composeTo || !composeSubject}
>
{sending ? 'Enviando...' : '📤 Enviar'}
</button>
<button className="toolbar-btn" onClick={() => setShowCompose(false)}>
Descartar
</button>
</div>
</div>
</div>
)}
{/* Compose dialog */}
<Dialog open={composeOpen} onOpenChange={(_, d) => setComposeOpen(d.open)}>
<DialogSurface>
<DialogBody>
<DialogContent>
<div style={{ padding: '8px 0', display: 'flex', flexDirection: 'column', gap: '4px' }}>
<div className={styles.composeField}>
<Label htmlFor="compose-to">Para</Label>
<Input id="compose-to" placeholder="destinatario@ejemplo.com" value={composeTo} onChange={(_, d) => setComposeTo(d.value)} />
</div>
<div className={styles.composeField}>
<Label htmlFor="compose-subject">Asunto</Label>
<Input id="compose-subject" placeholder="Asunto del mensaje" value={composeSubject} onChange={(_, d) => setComposeSubject(d.value)} />
</div>
<div className={styles.composeField}>
<Label htmlFor="compose-body">Mensaje</Label>
<Textarea id="compose-body" placeholder="Escribe tu mensaje aquí..." value={composeBody} onChange={(_, d) => setComposeBody(d.value)} style={{ minHeight: '160px' }} />
</div>
</div>
<div className={styles.composeFooter}>
<Button appearance="primary" icon={<Send24Regular />} onClick={handleSend} disabled={sending || !composeTo || !composeSubject}>
{sending ? 'Enviando…' : 'Enviar'}
</Button>
<Button appearance="subtle" onClick={() => setComposeOpen(false)}>Descartar</Button>
</div>
</DialogContent>
</DialogBody>
</DialogSurface>
</Dialog>
</div>
);
}
// Connections mockup — visualises how connectors work in code apps
function ConnectionsMockup() {
const styles = useStyles();
const connections = [
{ id: '1', name: 'shared_outlook', displayName: 'Outlook', connector: 'Microsoft Outlook', status: 'connected', color: '#0078D4' },
{ id: '2', name: 'shared_office365', displayName: 'Office 365 Users', connector: 'Office 365 Users', status: 'connected', color: '#D83B01' },
{ id: '3', name: 'shared_dataverse', displayName: 'Dataverse', connector: 'Microsoft Dataverse', status: 'connected', color: '#7719BA' },
{ id: '4', name: 'shared_azuresql', displayName: 'Azure SQL', connector: 'Azure SQL Database', status: 'disconnected', color: '#0078D4' },
];
return (
<div className={styles.connectionsPanel}>
<Text size={600} style={{ marginBottom: '4px', display: 'block', fontWeight: '700' }}>Connection references</Text>
<Text size={300} style={{ color: tokens.colorNeutralForeground3, marginBottom: '24px', display: 'block' }}>
Connection references are solution components that point to specific connections. Bind your code app to a reference instead of a direct connection to enable environment promotion.
</Text>
{/* Architecture diagram */}
<div style={{
background: tokens.colorNeutralBackground2,
border: `1px solid ${tokens.colorNeutralStroke2}`,
borderRadius: '8px',
padding: '20px 24px',
marginBottom: '24px',
display: 'flex',
alignItems: 'center',
gap: '12px',
flexWrap: 'wrap' as const,
}}>
{[
{ label: 'Tu Code App', bg: tokens.colorBrandBackground, color: tokens.colorBrandForeground1 },
{ label: 'PAC Client', bg: tokens.colorNeutralBackground4, color: tokens.colorNeutralForeground1 },
{ label: 'Connection Ref', bg: tokens.colorNeutralBackground4, color: tokens.colorNeutralForeground1 },
{ label: 'Connection', bg: tokens.colorNeutralBackground4, color: tokens.colorNeutralForeground1 },
{ label: 'Outlook / Dataverse', bg: '#7719BA', color: '#fff' },
].map((box, i) => (
<React.Fragment key={i}>
<div style={{
background: box.bg,
color: box.color,
padding: '8px 14px',
borderRadius: '6px',
fontSize: '13px',
fontWeight: '600',
whiteSpace: 'nowrap' as const,
}}>
{box.label}
</div>
{i < 4 && <div style={{ color: tokens.colorNeutralForeground3, fontSize: '16px' }}></div>}
</React.Fragment>
))}
</div>
{/* Connection cards */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))', gap: '12px', marginBottom: '32px' }}>
{connections.map(conn => (
<div
key={conn.id}
style={{
background: tokens.colorNeutralBackground2,
border: `1px solid ${tokens.colorNeutralStroke2}`,
borderRadius: '8px',
padding: '16px',
opacity: conn.status === 'connected' ? 1 : 0.55,
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '8px' }}>
<div style={{
width: '36px',
height: '36px',
borderRadius: '6px',
background: conn.color,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'white',
fontSize: '16px',
fontWeight: '700',
}}>
{conn.displayName[0]}
</div>
<div style={{ flex: 1 }}>
<Text weight="semibold" size={300}>{conn.displayName}</Text>
<Text size={200} style={{ color: tokens.colorNeutralForeground3 }}>{conn.connector}</Text>
</div>
<Badge appearance="filled" color={conn.status === 'connected' ? 'success' : 'danger'} size="small">
{conn.status === 'connected' ? 'Conectado' : 'Desconectado'}
</Badge>
</div>
<Text size={200} style={{ color: tokens.colorNeutralForeground3, fontFamily: 'monospace' }}>{conn.name}</Text>
</div>
))}
</div>
<Divider style={{ margin: '0 0 24px' }} />
<Text size={400} style={{ marginBottom: '12px', display: 'block', fontWeight: '600' }}>PAC CLI commands</Text>
<div className={styles.connCode}>
<div><span style={{ color: '#6b9fff' }}># Listar conexiones disponibles</span></div>
<div><span style={{ color: '#e0e0e0' }}>pac connection list</span></div>
<br />
<div><span style={{ color: '#6b9fff' }}># Añadir Outlook como data source</span></div>
<div><span style={{ color: '#e0e0e0' }}>pac code add-data-source -a shared_outlook ^</span></div>
<div><span style={{ color: '#e0e0e0', paddingLeft: '24px' }}>-c aaaaaaaa-0000-1111-bbbb-cccccccccccc</span></div>
<br />
<div><span style={{ color: '#6b9fff' }}># Añadir Dataverse (tabular)</span></div>
<div><span style={{ color: '#e0e0e0' }}>pac code add-data-source -a shared_dataverse ^</span></div>
<div><span style={{ color: '#e0e0e0', paddingLeft: '24px' }}>-c bbbbbbbb-2222-3333-cccc-dddddddddddd ^</span></div>
<div><span style={{ color: '#e0e0e0', paddingLeft: '24px' }}>-t accounts -d default</span></div>
</div>
</div>
);
}