Fix the frontend API calls and implement logins on frontend #7

Merged
dylan merged 24 commits from feat/update-frontend-api-calls into main 2026-03-04 20:20:50 +00:00
4 changed files with 127 additions and 6 deletions
Showing only changes of commit b6815c490a - Show all commits

View File

@@ -42,3 +42,24 @@ textarea:focus {
box-shadow: 0 0 0 3px var(--focus-ring);
outline: none;
}
@keyframes stats-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@keyframes stats-pulse {
0%,
100% {
opacity: 0.5;
}
50% {
opacity: 1;
}
}

View File

@@ -136,7 +136,27 @@ const StatPage = () => {
getStats();
}, [datasetId])
if (loading) return <p style={{...styles.page, minWidth: "100vh", minHeight: "100vh"}}>Loading insights</p>;
if (loading) {
return (
<div style={styles.loadingPage}>
<div style={styles.loadingCard}>
<div style={styles.loadingHeader}>
<div style={styles.loadingSpinner} />
<div>
<h2 style={styles.loadingTitle}>Loading analytics</h2>
<p style={styles.loadingSubtitle}>Fetching summary, timeline, user, and content insights.</p>
</div>
</div>
<div style={styles.loadingSkeleton}>
<div style={{ ...styles.loadingSkeletonLine, ...styles.loadingSkeletonLineLong }} />
<div style={{ ...styles.loadingSkeletonLine, ...styles.loadingSkeletonLineMed }} />
<div style={{ ...styles.loadingSkeletonLine, ...styles.loadingSkeletonLineShort }} />
</div>
</div>
</div>
);
}
if (error) return <p style={{...styles.page}}>{error}</p>;
return (

View File

@@ -2,6 +2,78 @@ import { palette } from "./palette";
import type { StyleMap } from "./types";
export const feedbackStyles: StyleMap = {
loadingPage: {
width: "100%",
minHeight: "100vh",
padding: 20,
display: "flex",
alignItems: "center",
justifyContent: "center",
},
loadingCard: {
width: "min(560px, 92vw)",
background: palette.surface,
border: `1px solid ${palette.borderDefault}`,
borderRadius: 8,
boxShadow: `0 1px 0 ${palette.shadowSubtle}`,
padding: 20,
},
loadingHeader: {
display: "flex",
alignItems: "center",
gap: 12,
},
loadingSpinner: {
width: 18,
height: 18,
borderRadius: "50%",
border: `2px solid ${palette.borderDefault}`,
borderTopColor: palette.brandGreen,
animation: "stats-spin 0.9s linear infinite",
flexShrink: 0,
},
loadingTitle: {
margin: 0,
fontSize: 16,
fontWeight: 600,
color: palette.textPrimary,
},
loadingSubtitle: {
margin: "6px 0 0",
fontSize: 13,
color: palette.textSecondary,
},
loadingSkeleton: {
marginTop: 16,
display: "grid",
gap: 8,
},
loadingSkeletonLine: {
height: 9,
borderRadius: 999,
background: palette.canvas,
animation: "stats-pulse 1.25s ease-in-out infinite",
},
loadingSkeletonLineLong: {
width: "100%",
},
loadingSkeletonLineMed: {
width: "78%",
},
loadingSkeletonLineShort: {
width: "62%",
},
alertCardError: {
borderColor: palette.alertErrorBorder,
background: palette.alertErrorBg,

View File

@@ -55,16 +55,24 @@ type TimeAnalysisResponse = {
}
// Content Analysis
type Emotion = {
emotion_anger: number;
emotion_disgust: number;
emotion_fear: number;
emotion_joy: number;
emotion_sadness: number;
};
type NGram = {
count: number;
ngram: string;
}
type AverageEmotionByTopic = {
topic: string;
n: number;
[emotion: string]: string | number;
}
type AverageEmotionByTopic = Emotion & {
n: number;
topic: string;
};
type ContentAnalysisResponse = {
word_frequencies: FrequencyWord[];