-
Upload Dataset
-
+
Upload Dataset
+
Name your dataset, then upload posts and topic map files to generate analytics.
@@ -96,10 +96,10 @@ const UploadPage = () => {
}}
>
-
Dataset Name
+
Dataset Name
Use a clear label so you can identify this upload later.
{
-
Posts File (.jsonl)
+
Posts File (.jsonl)
Upload the raw post records export.
setPostFile(event.target.files?.[0] ?? null)}
/>
-
+
{postFile ? `Selected: ${postFile.name}` : "No file selected"}
-
Topics File (.json)
+
Topics File (.json)
Upload your topic bucket mapping file.
setTopicBucketFile(event.target.files?.[0] ?? null)}
/>
-
+
{topicBucketFile ? `Selected: ${topicBucketFile.name}` : "No file selected"}
@@ -140,10 +140,7 @@ const UploadPage = () => {
style={{
...styles.card,
marginTop: 14,
- borderColor: hasError ? "rgba(185, 28, 28, 0.28)" : "rgba(0,0,0,0.06)",
- background: hasError ? "#fff5f5" : "#ffffff",
- color: hasError ? "#991b1b" : "#374151",
- fontSize: 14,
+ ...(hasError ? styles.alertCardError : styles.alertCardInfo),
}}
>
{returnMessage || "After upload, your dataset is queued for processing and you'll land on stats."}
diff --git a/frontend/src/styles/stats/appLayout.ts b/frontend/src/styles/stats/appLayout.ts
new file mode 100644
index 0000000..b680ea6
--- /dev/null
+++ b/frontend/src/styles/stats/appLayout.ts
@@ -0,0 +1,42 @@
+import { palette } from "./palette";
+import type { StyleMap } from "./types";
+
+export const appLayoutStyles: StyleMap = {
+ appHeaderWrap: {
+ padding: "16px 24px 0",
+ },
+
+ appHeaderBrandRow: {
+ display: "flex",
+ alignItems: "center",
+ gap: 10,
+ flexWrap: "wrap",
+ },
+
+ appTitle: {
+ margin: 0,
+ color: palette.textPrimary,
+ fontSize: 18,
+ fontWeight: 600,
+ },
+
+ authStatusBadge: {
+ padding: "3px 8px",
+ borderRadius: 6,
+ fontSize: 12,
+ fontWeight: 600,
+ fontFamily: '"IBM Plex Sans", "Noto Sans", "Liberation Sans", "Segoe UI", sans-serif',
+ },
+
+ authStatusSignedIn: {
+ border: `1px solid ${palette.statusPositiveBorder}`,
+ background: palette.statusPositiveBg,
+ color: palette.statusPositiveText,
+ },
+
+ authStatusSignedOut: {
+ border: `1px solid ${palette.statusNegativeBorder}`,
+ background: palette.statusNegativeBg,
+ color: palette.statusNegativeText,
+ },
+};
diff --git a/frontend/src/styles/stats/auth.ts b/frontend/src/styles/stats/auth.ts
new file mode 100644
index 0000000..abc82b5
--- /dev/null
+++ b/frontend/src/styles/stats/auth.ts
@@ -0,0 +1,92 @@
+import { palette } from "./palette";
+import type { StyleMap } from "./types";
+
+export const authStyles: StyleMap = {
+ containerAuth: {
+ maxWidth: 560,
+ margin: "0 auto",
+ padding: "48px 24px",
+ },
+
+ headingXl: {
+ margin: 0,
+ color: palette.textPrimary,
+ fontSize: 28,
+ fontWeight: 600,
+ lineHeight: 1.1,
+ },
+
+ headingBlock: {
+ marginBottom: 22,
+ textAlign: "center",
+ },
+
+ mutedText: {
+ margin: "8px 0 0",
+ color: palette.textSecondary,
+ fontSize: 14,
+ },
+
+ authCard: {
+ padding: 28,
+ },
+
+ authForm: {
+ display: "grid",
+ gap: 12,
+ maxWidth: 380,
+ margin: "0 auto",
+ },
+
+ inputFullWidth: {
+ width: "100%",
+ maxWidth: "100%",
+ boxSizing: "border-box",
+ },
+
+ authControl: {
+ width: "100%",
+ maxWidth: "100%",
+ boxSizing: "border-box",
+ },
+
+ authErrorText: {
+ color: palette.dangerText,
+ margin: "12px auto 0",
+ fontSize: 14,
+ maxWidth: 380,
+ textAlign: "center",
+ },
+
+ authInfoText: {
+ color: palette.successText,
+ margin: "12px auto 0",
+ fontSize: 14,
+ maxWidth: 380,
+ textAlign: "center",
+ },
+
+ authSwitchRow: {
+ marginTop: 16,
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ gap: 8,
+ flexWrap: "wrap",
+ },
+
+ authSwitchLabel: {
+ color: palette.textSecondary,
+ fontSize: 14,
+ },
+
+ authSwitchButton: {
+ border: "none",
+ background: "transparent",
+ color: palette.brandGreenBorder,
+ fontSize: 14,
+ fontWeight: 600,
+ cursor: "pointer",
+ padding: 0,
+ },
+};
diff --git a/frontend/src/styles/stats/cards.ts b/frontend/src/styles/stats/cards.ts
new file mode 100644
index 0000000..ab388a9
--- /dev/null
+++ b/frontend/src/styles/stats/cards.ts
@@ -0,0 +1,42 @@
+import { palette } from "./palette";
+import type { StyleMap } from "./types";
+
+export const cardStyles: StyleMap = {
+ cardBase: {
+ background: palette.surface,
+ border: `1px solid ${palette.borderDefault}`,
+ borderRadius: 8,
+ padding: 14,
+ boxShadow: `0 1px 0 ${palette.shadowSubtle}`,
+ minHeight: 88,
+ },
+
+ cardTopRow: {
+ display: "flex",
+ justifyContent: "space-between",
+ alignItems: "center",
+ gap: 10,
+ },
+
+ cardLabel: {
+ fontSize: 12,
+ fontWeight: 600,
+ color: palette.textSecondary,
+ letterSpacing: "0.02em",
+ textTransform: "uppercase",
+ },
+
+ cardValue: {
+ fontSize: 24,
+ fontWeight: 700,
+ marginTop: 6,
+ letterSpacing: "-0.02em",
+ color: palette.textPrimary,
+ },
+
+ cardSubLabel: {
+ marginTop: 6,
+ fontSize: 12,
+ color: palette.textSecondary,
+ },
+};
diff --git a/frontend/src/styles/stats/datasets.ts b/frontend/src/styles/stats/datasets.ts
new file mode 100644
index 0000000..a2e2d28
--- /dev/null
+++ b/frontend/src/styles/stats/datasets.ts
@@ -0,0 +1,55 @@
+import { palette } from "./palette";
+import type { StyleMap } from "./types";
+
+export const datasetStyles: StyleMap = {
+ sectionHeaderTitle: {
+ margin: 0,
+ color: palette.textPrimary,
+ fontSize: 28,
+ fontWeight: 600,
+ },
+
+ sectionHeaderSubtitle: {
+ margin: "8px 0 0",
+ color: palette.textSecondary,
+ fontSize: 14,
+ },
+
+ listNoBullets: {
+ listStyle: "none",
+ margin: 0,
+ padding: 0,
+ },
+
+ datasetListItem: {
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "space-between",
+ gap: 12,
+ padding: "14px 16px",
+ borderBottom: `1px solid ${palette.borderMuted}`,
+ },
+
+ datasetName: {
+ fontWeight: 600,
+ color: palette.textPrimary,
+ },
+
+ datasetMeta: {
+ fontSize: 13,
+ color: palette.textSecondary,
+ marginTop: 4,
+ },
+
+ datasetMetaSecondary: {
+ fontSize: 13,
+ color: palette.textSecondary,
+ marginTop: 2,
+ },
+
+ subtleBodyText: {
+ margin: "10px 0 0",
+ fontSize: 13,
+ color: palette.textBody,
+ },
+};
diff --git a/frontend/src/styles/stats/emotional.ts b/frontend/src/styles/stats/emotional.ts
new file mode 100644
index 0000000..6ad68ce
--- /dev/null
+++ b/frontend/src/styles/stats/emotional.ts
@@ -0,0 +1,51 @@
+import { palette } from "./palette";
+import type { StyleMap } from "./types";
+
+export const emotionalStyles: StyleMap = {
+ emotionalSummaryRow: {
+ display: "flex",
+ flexWrap: "wrap",
+ gap: 10,
+ fontSize: 13,
+ color: palette.textTertiary,
+ marginTop: 6,
+ },
+
+ emotionalTopicLabel: {
+ fontSize: 12,
+ fontWeight: 600,
+ color: palette.textSecondary,
+ letterSpacing: "0.02em",
+ textTransform: "uppercase",
+ },
+
+ emotionalTopicValue: {
+ fontSize: 24,
+ fontWeight: 800,
+ marginTop: 4,
+ lineHeight: 1.2,
+ },
+
+ emotionalMetricRow: {
+ display: "flex",
+ justifyContent: "space-between",
+ alignItems: "center",
+ marginTop: 10,
+ fontSize: 13,
+ color: palette.textSecondary,
+ },
+
+ emotionalMetricRowCompact: {
+ display: "flex",
+ justifyContent: "space-between",
+ alignItems: "center",
+ marginTop: 4,
+ fontSize: 13,
+ color: palette.textSecondary,
+ },
+
+ emotionalMetricValue: {
+ fontWeight: 600,
+ color: palette.textPrimary,
+ },
+};
diff --git a/frontend/src/styles/stats/feedback.ts b/frontend/src/styles/stats/feedback.ts
new file mode 100644
index 0000000..33cbe2d
--- /dev/null
+++ b/frontend/src/styles/stats/feedback.ts
@@ -0,0 +1,34 @@
+import { palette } from "./palette";
+import type { StyleMap } from "./types";
+
+export const feedbackStyles: StyleMap = {
+ alertCardError: {
+ borderColor: palette.alertErrorBorder,
+ background: palette.alertErrorBg,
+ color: palette.alertErrorText,
+ fontSize: 14,
+ },
+
+ alertCardInfo: {
+ borderColor: palette.alertInfoBorder,
+ background: palette.surface,
+ color: palette.textBody,
+ fontSize: 14,
+ },
+
+ statusMessageCard: {
+ marginTop: 12,
+ boxShadow: "none",
+ },
+
+ dashboardMeta: {
+ fontSize: 13,
+ color: palette.textSecondary,
+ },
+
+ tabsRow: {
+ display: "flex",
+ gap: 8,
+ marginTop: 12,
+ },
+};
diff --git a/frontend/src/styles/stats/foundations.ts b/frontend/src/styles/stats/foundations.ts
new file mode 100644
index 0000000..7801824
--- /dev/null
+++ b/frontend/src/styles/stats/foundations.ts
@@ -0,0 +1,157 @@
+import { palette } from "./palette";
+import type { StyleMap } from "./types";
+
+export const foundationStyles: StyleMap = {
+ appShell: {
+ minHeight: "100vh",
+ background: palette.canvas,
+ fontFamily: '"IBM Plex Sans", "Noto Sans", "Liberation Sans", "Segoe UI", sans-serif',
+ color: palette.textPrimary,
+ },
+
+ page: {
+ width: "100%",
+ minHeight: "100vh",
+ padding: 20,
+ background: palette.canvas,
+ fontFamily: '"IBM Plex Sans", "Noto Sans", "Liberation Sans", "Segoe UI", sans-serif',
+ color: palette.textPrimary,
+ overflowX: "hidden",
+ boxSizing: "border-box",
+ },
+
+ container: {
+ maxWidth: 1240,
+ margin: "0 auto",
+ },
+
+ containerWide: {
+ maxWidth: 1100,
+ margin: "0 auto",
+ },
+
+ containerNarrow: {
+ maxWidth: 720,
+ margin: "0 auto",
+ },
+
+ card: {
+ background: palette.surface,
+ borderRadius: 8,
+ padding: 16,
+ border: `1px solid ${palette.borderDefault}`,
+ boxShadow: `0 1px 0 ${palette.shadowSubtle}`,
+ },
+
+ headerBar: {
+ display: "flex",
+ flexWrap: "wrap",
+ alignItems: "center",
+ justifyContent: "space-between",
+ gap: 10,
+ },
+
+ controls: {
+ display: "flex",
+ gap: 8,
+ alignItems: "center",
+ },
+
+ controlsWrapped: {
+ display: "flex",
+ gap: 8,
+ alignItems: "center",
+ flexWrap: "wrap",
+ },
+
+ input: {
+ width: 280,
+ maxWidth: "70vw",
+ padding: "8px 10px",
+ borderRadius: 6,
+ border: `1px solid ${palette.borderDefault}`,
+ outline: "none",
+ fontSize: 14,
+ background: palette.surface,
+ color: palette.textPrimary,
+ },
+
+ buttonPrimary: {
+ padding: "8px 12px",
+ borderRadius: 6,
+ border: `1px solid ${palette.brandGreenBorder}`,
+ background: palette.brandGreen,
+ color: palette.surface,
+ fontWeight: 600,
+ cursor: "pointer",
+ boxShadow: "none",
+ },
+
+ buttonSecondary: {
+ padding: "8px 12px",
+ borderRadius: 6,
+ border: `1px solid ${palette.borderDefault}`,
+ background: palette.canvas,
+ color: palette.textPrimary,
+ fontWeight: 600,
+ cursor: "pointer",
+ },
+
+ grid: {
+ marginTop: 12,
+ display: "grid",
+ gridTemplateColumns: "repeat(12, 1fr)",
+ gap: 12,
+ },
+
+ sectionTitle: {
+ margin: 0,
+ fontSize: 17,
+ fontWeight: 600,
+ },
+
+ sectionSubtitle: {
+ margin: "6px 0 14px",
+ fontSize: 13,
+ color: palette.textSecondary,
+ },
+
+ chartWrapper: {
+ width: "100%",
+ height: 350,
+ },
+
+ heatmapWrapper: {
+ width: "100%",
+ height: 320,
+ },
+
+ topUsersList: {
+ display: "flex",
+ flexDirection: "column",
+ gap: 10,
+ },
+
+ topUserItem: {
+ padding: "10px 12px",
+ borderRadius: 8,
+ background: palette.canvas,
+ border: `1px solid ${palette.borderMuted}`,
+ },
+
+ topUserName: {
+ fontWeight: 600,
+ fontSize: 14,
+ color: palette.textPrimary,
+ },
+
+ topUserMeta: {
+ fontSize: 13,
+ color: palette.textSecondary,
+ },
+
+ scrollArea: {
+ maxHeight: 420,
+ overflowY: "auto",
+ },
+};
diff --git a/frontend/src/styles/stats/modal.ts b/frontend/src/styles/stats/modal.ts
new file mode 100644
index 0000000..596ee00
--- /dev/null
+++ b/frontend/src/styles/stats/modal.ts
@@ -0,0 +1,28 @@
+import { palette } from "./palette";
+import type { StyleMap } from "./types";
+
+export const modalStyles: StyleMap = {
+ modalRoot: {
+ position: "relative",
+ zIndex: 50,
+ },
+
+ modalBackdrop: {
+ position: "fixed",
+ inset: 0,
+ background: palette.modalBackdrop,
+ },
+
+ modalContainer: {
+ position: "fixed",
+ inset: 0,
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ padding: 16,
+ },
+
+ modalPanel: {
+ width: "min(520px, 95vw)",
+ },
+};
diff --git a/frontend/src/styles/stats/palette.ts b/frontend/src/styles/stats/palette.ts
new file mode 100644
index 0000000..a4943ae
--- /dev/null
+++ b/frontend/src/styles/stats/palette.ts
@@ -0,0 +1,26 @@
+export const palette = {
+ canvas: "#f6f8fa",
+ surface: "#ffffff",
+ textPrimary: "#24292f",
+ textSecondary: "#57606a",
+ textTertiary: "#4b5563",
+ textBody: "#374151",
+ borderDefault: "#d0d7de",
+ borderMuted: "#d8dee4",
+ shadowSubtle: "rgba(27, 31, 36, 0.04)",
+ brandGreen: "#2da44e",
+ brandGreenBorder: "#1f883d",
+ statusPositiveBorder: "#b7dfc8",
+ statusPositiveBg: "#edf9f1",
+ statusPositiveText: "#1f6f43",
+ statusNegativeBorder: "#f3c1c1",
+ statusNegativeBg: "#fff2f2",
+ statusNegativeText: "#9a2929",
+ dangerText: "#b91c1c",
+ successText: "#166534",
+ alertErrorBorder: "rgba(185, 28, 28, 0.28)",
+ alertErrorBg: "#fff5f5",
+ alertErrorText: "#991b1b",
+ alertInfoBorder: "rgba(0,0,0,0.06)",
+ modalBackdrop: "rgba(0,0,0,0.45)",
+} as const;
diff --git a/frontend/src/styles/stats/types.ts b/frontend/src/styles/stats/types.ts
new file mode 100644
index 0000000..74d3d2e
--- /dev/null
+++ b/frontend/src/styles/stats/types.ts
@@ -0,0 +1,3 @@
+import type { CSSProperties } from "react";
+
+export type StyleMap = Record
;
diff --git a/frontend/src/styles/stats_styling.tsx b/frontend/src/styles/stats_styling.tsx
index e942a7f..9397214 100644
--- a/frontend/src/styles/stats_styling.tsx
+++ b/frontend/src/styles/stats_styling.tsx
@@ -1,136 +1,22 @@
import type { CSSProperties } from "react";
+import { appLayoutStyles } from "./stats/appLayout";
+import { authStyles } from "./stats/auth";
+import { cardStyles } from "./stats/cards";
+import { datasetStyles } from "./stats/datasets";
+import { emotionalStyles } from "./stats/emotional";
+import { feedbackStyles } from "./stats/feedback";
+import { foundationStyles } from "./stats/foundations";
+import { modalStyles } from "./stats/modal";
const StatsStyling: Record = {
- page: {
- width: "100%",
- minHeight: "100vh",
- padding: 24,
- background: "#f6f7fb",
- fontFamily:
- '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Inter, Arial, sans-serif',
- color: "#111827",
- overflowX: "hidden",
- boxSizing: "border-box"
- },
-
-
- container: {
- maxWidth: 1400,
- margin: "0 auto",
- },
-
- card: {
- background: "white",
- borderRadius: 16,
- padding: 16,
- border: "1px solid rgba(0,0,0,0.06)",
- boxShadow: "0 6px 20px rgba(0,0,0,0.06)",
- },
-
- headerBar: {
- display: "flex",
- flexWrap: "wrap",
- alignItems: "center",
- justifyContent: "space-between",
- gap: 12,
- },
-
- controls: {
- display: "flex",
- gap: 10,
- alignItems: "center",
- },
-
- input: {
- width: 320,
- maxWidth: "70vw",
- padding: "10px 12px",
- borderRadius: 12,
- border: "1px solid rgba(0,0,0,0.12)",
- outline: "none",
- fontSize: 14,
- background: "#fff",
- color: "black"
- },
-
- buttonPrimary: {
- padding: "10px 14px",
- borderRadius: 12,
- border: "1px solid rgba(0,0,0,0.08)",
- background: "#2563eb",
- color: "white",
- fontWeight: 600,
- cursor: "pointer",
- boxShadow: "0 6px 16px rgba(37,99,235,0.25)",
- },
-
- buttonSecondary: {
- padding: "10px 14px",
- borderRadius: 12,
- border: "1px solid rgba(0,0,0,0.12)",
- background: "#fff",
- color: "#111827",
- fontWeight: 600,
- cursor: "pointer",
- },
-
- grid: {
- marginTop: 18,
- display: "grid",
- gridTemplateColumns: "repeat(12, 1fr)",
- gap: 16,
- },
-
- sectionTitle: {
- margin: 0,
- fontSize: 16,
- fontWeight: 700,
- },
-
- sectionSubtitle: {
- margin: "6px 0 14px",
- fontSize: 13,
- color: "#6b7280",
- },
-
- chartWrapper: {
- width: "100%",
- height: 350,
- },
-
- heatmapWrapper: {
- width: "100%",
- height: 320,
- },
-
- topUsersList: {
- display: "flex",
- flexDirection: "column",
- gap: 10,
- },
-
- topUserItem: {
- padding: "10px 12px",
- borderRadius: 12,
- background: "#f9fafb",
- border: "1px solid rgba(0,0,0,0.06)",
- },
-
- topUserName: {
- fontWeight: 700,
- fontSize: 14,
- color: "black"
- },
-
- topUserMeta: {
- fontSize: 13,
- color: "#6b7280",
- },
-
- scrollArea: {
- maxHeight: 450,
- overflowY: "auto",
- },
+ ...foundationStyles,
+ ...appLayoutStyles,
+ ...authStyles,
+ ...datasetStyles,
+ ...feedbackStyles,
+ ...cardStyles,
+ ...emotionalStyles,
+ ...modalStyles,
};
-export default StatsStyling;
\ No newline at end of file
+export default StatsStyling;