INSIGHT-MVP/packages/frontend/src/crm/contacts/EmailsTab.tsx
Thomas Reitz 1ecd7dad82 fix(ms365): OAuth-Connect via API-Call statt direktem Browser-Link
Problem: <a href="/api/v1/auth/integrations/microsoft-365"> sendet keinen
JWT-Authorization-Header (JWT liegt im Memory, nicht als Cookie).

Lösung:
- Backend: initM365Integration gibt JSON {url} zurück statt server-redirect
- Frontend: integrationsApi.connectM365() ruft Endpoint via Axios ab, dann
  window.location.href zur OAuth-URL
- ProfilePage + EmailsTab + CalendarTab + TasksTab: <a href> → <button onClick>

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 22:57:20 +01:00

122 lines
4 KiB
TypeScript

import { useContactEmails, useIntegrations } from '../hooks';
import { integrationsApi } from '../api';
import type { M365Email } from '../types';
interface Props {
contactId: string;
}
function formatEmailDate(iso: string): string {
return new Date(iso).toLocaleString('de-DE', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
});
}
export function EmailsTab({ contactId }: Props) {
const { data: integrationsData } = useIntegrations();
const isConnected = integrationsData?.data?.some(
(i) => i.provider === 'MICROSOFT_365' && i.connected,
) ?? false;
const { data, isLoading, error } = useContactEmails(contactId);
const emails: M365Email[] = data?.data ?? [];
if (!isConnected) {
return (
<div style={{ padding: '1.5rem 0', textAlign: 'center' }}>
<p style={{ color: 'var(--color-text-muted)', fontSize: '0.9375rem', marginBottom: '1rem' }}>
Verbinden Sie Microsoft 365, um E-Mails zu diesem Kontakt zu sehen.
</p>
<button
type="button"
onClick={() => integrationsApi.connectM365()}
style={{
display: 'inline-flex',
alignItems: 'center',
gap: '0.5rem',
padding: '0.4375rem 1rem',
background: 'var(--color-primary)',
color: 'white',
border: 'none',
borderRadius: 'var(--radius-sm)',
fontSize: '0.875rem',
fontWeight: 600,
cursor: 'pointer',
}}
>
Microsoft 365 verbinden
</button>
</div>
);
}
if (isLoading) {
return <p style={{ color: 'var(--color-text-muted)', fontSize: '0.875rem', padding: '1rem 0' }}>Laden</p>;
}
if (error) {
return <p style={{ color: 'var(--color-error)', fontSize: '0.875rem', padding: '1rem 0' }}>E-Mails konnten nicht geladen werden.</p>;
}
if (emails.length === 0) {
return <p style={{ color: 'var(--color-text-muted)', fontSize: '0.875rem', padding: '1rem 0' }}>Keine E-Mails gefunden.</p>;
}
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
{emails.map((email) => (
<a
key={email.id}
href={email.webLink}
target="_blank"
rel="noopener noreferrer"
style={{
display: 'block',
padding: '0.75rem 1rem',
background: email.isRead ? 'var(--color-bg-card)' : 'var(--color-bg-subtle, var(--color-bg-card))',
border: '1px solid var(--color-border)',
borderRadius: 'var(--radius-sm)',
textDecoration: 'none',
borderLeft: email.isRead ? undefined : '3px solid var(--color-primary)',
}}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: '0.5rem' }}>
<span style={{
fontSize: '0.875rem',
fontWeight: email.isRead ? 400 : 600,
color: 'var(--color-text)',
flex: 1,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}>
{email.subject || '(kein Betreff)'}
</span>
<span style={{ fontSize: '0.75rem', color: 'var(--color-text-muted)', flexShrink: 0 }}>
{formatEmailDate(email.receivedDateTime)}
</span>
</div>
<div style={{ fontSize: '0.8125rem', color: 'var(--color-text-muted)', marginTop: '0.25rem' }}>
Von: {email.from?.emailAddress?.name ?? email.from?.emailAddress?.address ?? '—'}
</div>
{email.bodyPreview && (
<div style={{
fontSize: '0.8125rem',
color: 'var(--color-text-secondary)',
marginTop: '0.25rem',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}>
{email.bodyPreview}
</div>
)}
</a>
))}
</div>
);
}