Fix the frontend API calls and implement logins on frontend #7
@@ -91,36 +91,24 @@ const AppLayout = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
minHeight: "100vh",
|
||||
background: "#f6f7fb",
|
||||
fontFamily: styles.page.fontFamily,
|
||||
color: "#111827",
|
||||
}}
|
||||
>
|
||||
<div style={{ ...styles.container, padding: "16px 24px 0" }}>
|
||||
<div style={styles.appShell}>
|
||||
<div style={{ ...styles.container, ...styles.appHeaderWrap }}>
|
||||
<div style={{ ...styles.card, ...styles.headerBar }}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" }}>
|
||||
<span style={{ margin: 0, color: "#111827", fontSize: 18, fontWeight: 700 }}>
|
||||
<div style={styles.appHeaderBrandRow}>
|
||||
<span style={styles.appTitle}>
|
||||
Ethnograph View
|
||||
</span>
|
||||
<span
|
||||
style={{
|
||||
padding: "4px 10px",
|
||||
borderRadius: 999,
|
||||
fontSize: 12,
|
||||
fontWeight: 700,
|
||||
fontFamily: styles.page.fontFamily,
|
||||
background: isSignedIn ? "#dcfce7" : "#fee2e2",
|
||||
color: isSignedIn ? "#166534" : "#991b1b",
|
||||
...styles.authStatusBadge,
|
||||
...(isSignedIn ? styles.authStatusSignedIn : styles.authStatusSignedOut),
|
||||
}}
|
||||
>
|
||||
{isSignedIn ? `Signed in: ${getUserLabel(currentUser)}` : "Not signed in"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div style={{ ...styles.controls, flexWrap: "wrap" }}>
|
||||
<div style={styles.controlsWrapped}>
|
||||
<button
|
||||
type="button"
|
||||
style={location.pathname === "/upload" ? styles.buttonPrimary : styles.buttonSecondary}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import type { CSSProperties } from "react";
|
||||
import StatsStyling from "../styles/stats_styling";
|
||||
|
||||
const styles = StatsStyling;
|
||||
|
||||
const Card = (props: {
|
||||
label: string;
|
||||
@@ -8,43 +11,15 @@ const Card = (props: {
|
||||
style?: CSSProperties
|
||||
}) => {
|
||||
return (
|
||||
<div style={{
|
||||
background: "rgba(255,255,255,0.85)",
|
||||
border: "1px solid rgba(15,23,42,0.08)",
|
||||
borderRadius: 16,
|
||||
padding: 14,
|
||||
boxShadow: "0 12px 30px rgba(15,23,42,0.06)",
|
||||
minHeight: 88,
|
||||
...props.style
|
||||
}}>
|
||||
<div style={ {
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
gap: 10,
|
||||
}}>
|
||||
<div style={{
|
||||
fontSize: 12,
|
||||
fontWeight: 700,
|
||||
color: "rgba(15, 23, 42, 0.65)",
|
||||
letterSpacing: "0.02em",
|
||||
textTransform: "uppercase"
|
||||
}}>
|
||||
<div style={{ ...styles.cardBase, ...props.style }}>
|
||||
<div style={styles.cardTopRow}>
|
||||
<div style={styles.cardLabel}>
|
||||
{props.label}
|
||||
</div>
|
||||
{props.rightSlot ? <div>{props.rightSlot}</div> : null}
|
||||
</div>
|
||||
<div style={{
|
||||
fontSize: 22,
|
||||
fontWeight: 850,
|
||||
marginTop: 6,
|
||||
letterSpacing: "-0.02em",
|
||||
}}>{props.value}</div>
|
||||
{props.sublabel ? <div style={{
|
||||
marginTop: 6,
|
||||
fontSize: 12,
|
||||
color: "rgba(15, 23, 42, 0.55)",
|
||||
}}>{props.sublabel}</div> : null}
|
||||
<div style={styles.cardValue}>{props.value}</div>
|
||||
{props.sublabel ? <div style={styles.cardSubLabel}>{props.sublabel}</div> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -66,11 +66,11 @@ const EmotionalStats = ({contentData}: EmotionalStatsProps) => {
|
||||
<div style={{ ...styles.container, ...styles.card, marginTop: 16 }}>
|
||||
<h2 style={styles.sectionTitle}>Average Emotion by Topic</h2>
|
||||
<p style={styles.sectionSubtitle}>Read confidence together with sample size. Topics with fewer than {lowSampleThreshold} events are usually noisy and less reliable.</p>
|
||||
<div style={{ display: "flex", flexWrap: "wrap", gap: 10, fontSize: 13, color: "#4b5563", marginTop: 6 }}>
|
||||
<span><strong style={{ color: "#111827" }}>Topics:</strong> {strongestPerTopic.length}</span>
|
||||
<span><strong style={{ color: "#111827" }}>Median Sample:</strong> {medianSampleSize} events</span>
|
||||
<span><strong style={{ color: "#111827" }}>Low Sample (<{lowSampleThreshold}):</strong> {lowSampleTopics}</span>
|
||||
<span><strong style={{ color: "#111827" }}>Stable Sample ({stableSampleThreshold}+):</strong> {stableSampleTopics}</span>
|
||||
<div style={styles.emotionalSummaryRow}>
|
||||
<span><strong style={{ color: "#24292f" }}>Topics:</strong> {strongestPerTopic.length}</span>
|
||||
<span><strong style={{ color: "#24292f" }}>Median Sample:</strong> {medianSampleSize} events</span>
|
||||
<span><strong style={{ color: "#24292f" }}>Low Sample (<{lowSampleThreshold}):</strong> {lowSampleTopics}</span>
|
||||
<span><strong style={{ color: "#24292f" }}>Stable Sample ({stableSampleThreshold}+):</strong> {stableSampleTopics}</span>
|
||||
</div>
|
||||
<p style={{ ...styles.sectionSubtitle, marginTop: 10, marginBottom: 0 }}>
|
||||
Confidence reflects how strongly one emotion leads within a topic, not model accuracy. Use larger samples for stronger conclusions.
|
||||
@@ -81,19 +81,19 @@ const EmotionalStats = ({contentData}: EmotionalStatsProps) => {
|
||||
{strongestPerTopic.map((topic) => (
|
||||
<div key={topic.topic} style={{ ...styles.card, gridColumn: "span 4" }}>
|
||||
<h3 style={{ ...styles.sectionTitle, marginBottom: 6 }}>{topic.topic}</h3>
|
||||
<div style={{ fontSize: 12, fontWeight: 700, color: "#6b7280", letterSpacing: "0.02em", textTransform: "uppercase" }}>
|
||||
<div style={styles.emotionalTopicLabel}>
|
||||
Top Emotion
|
||||
</div>
|
||||
<div style={{ fontSize: 24, fontWeight: 800, marginTop: 4, lineHeight: 1.2 }}>
|
||||
<div style={styles.emotionalTopicValue}>
|
||||
{formatEmotion(topic.emotion)}
|
||||
</div>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginTop: 10, fontSize: 13, color: "#6b7280" }}>
|
||||
<div style={styles.emotionalMetricRow}>
|
||||
<span>Confidence</span>
|
||||
<span style={{ fontWeight: 700, color: "#111827" }}>{topic.value.toFixed(3)}</span>
|
||||
<span style={styles.emotionalMetricValue}>{topic.value.toFixed(3)}</span>
|
||||
</div>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginTop: 4, fontSize: 13, color: "#6b7280" }}>
|
||||
<div style={styles.emotionalMetricRowCompact}>
|
||||
<span>Sample Size</span>
|
||||
<span style={{ fontWeight: 700, color: "#111827" }}>{topic.count} events</span>
|
||||
<span style={styles.emotionalMetricValue}>{topic.count} events</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -13,26 +13,11 @@ type Props = {
|
||||
|
||||
export default function UserModal({ open, onClose, userData, username }: Props) {
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} style={{ position: "relative", zIndex: 50 }}>
|
||||
<div
|
||||
style={{
|
||||
position: "fixed",
|
||||
inset: 0,
|
||||
background: "rgba(0,0,0,0.45)",
|
||||
}}
|
||||
/>
|
||||
<Dialog open={open} onClose={onClose} style={styles.modalRoot}>
|
||||
<div style={styles.modalBackdrop} />
|
||||
|
||||
<div
|
||||
style={{
|
||||
position: "fixed",
|
||||
inset: 0,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
padding: 16,
|
||||
}}
|
||||
>
|
||||
<DialogPanel style={{ ...styles.card, width: "min(520px, 95vw)" }}>
|
||||
<div style={styles.modalContainer}>
|
||||
<DialogPanel style={{ ...styles.card, ...styles.modalPanel }}>
|
||||
<div style={styles.headerBar}>
|
||||
<div>
|
||||
<DialogTitle style={styles.sectionTitle}>{username}</DialogTitle>
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
:root {
|
||||
--bg-default: #f6f8fa;
|
||||
--text-default: #24292f;
|
||||
--border-default: #d0d7de;
|
||||
--focus-ring: rgba(9, 105, 218, 0.22);
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
@@ -14,4 +18,27 @@ body,
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--bg-default);
|
||||
color: var(--text-default);
|
||||
font-family: "IBM Plex Sans", "Noto Sans", "Liberation Sans", "Segoe UI", sans-serif;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
input:focus,
|
||||
button:focus-visible,
|
||||
select:focus,
|
||||
textarea:focus {
|
||||
border-color: #0969da;
|
||||
box-shadow: 0 0 0 3px var(--focus-ring);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@@ -78,9 +78,9 @@ const DatasetStatusPage = () => {
|
||||
|
||||
return (
|
||||
<div style={styles.page}>
|
||||
<div style={{ ...styles.container, maxWidth: 720 }}>
|
||||
<div style={styles.containerNarrow}>
|
||||
<div style={{ ...styles.card, marginTop: 28 }}>
|
||||
<h1 style={{ margin: 0, fontSize: 28, color: "#111827" }}>
|
||||
<h1 style={styles.sectionHeaderTitle}>
|
||||
{isProcessing ? "Processing dataset..." : isError ? "Dataset processing failed" : "Dataset ready"}
|
||||
</h1>
|
||||
|
||||
@@ -94,11 +94,10 @@ const DatasetStatusPage = () => {
|
||||
<div
|
||||
style={{
|
||||
...styles.card,
|
||||
marginTop: 12,
|
||||
...styles.statusMessageCard,
|
||||
borderColor: isError ? "rgba(185, 28, 28, 0.28)" : "rgba(0,0,0,0.06)",
|
||||
background: isError ? "#fff5f5" : "#ffffff",
|
||||
color: isError ? "#991b1b" : "#374151",
|
||||
boxShadow: "none",
|
||||
}}
|
||||
>
|
||||
{statusMessage || (isProcessing ? "Waiting for updates from the worker queue..." : "No details provided.")}
|
||||
|
||||
@@ -55,11 +55,11 @@ const DatasetsPage = () => {
|
||||
|
||||
return (
|
||||
<div style={styles.page}>
|
||||
<div style={{ ...styles.container, maxWidth: 1100 }}>
|
||||
<div style={styles.containerWide}>
|
||||
<div style={{ ...styles.card, ...styles.headerBar }}>
|
||||
<div>
|
||||
<h1 style={{ margin: 0, color: "#111827", fontSize: 28 }}>My Datasets</h1>
|
||||
<p style={{ margin: "8px 0 0", color: "#6b7280", fontSize: 14 }}>
|
||||
<h1 style={styles.sectionHeaderTitle}>My Datasets</h1>
|
||||
<p style={styles.sectionHeaderSubtitle}>
|
||||
View and reopen datasets you previously uploaded.
|
||||
</p>
|
||||
</div>
|
||||
@@ -91,7 +91,7 @@ const DatasetsPage = () => {
|
||||
|
||||
{!error && datasets.length > 0 && (
|
||||
<div style={{ ...styles.card, marginTop: 14, padding: 0, overflow: "hidden" }}>
|
||||
<ul style={{ listStyle: "none", margin: 0, padding: 0 }}>
|
||||
<ul style={styles.listNoBullets}>
|
||||
{datasets.map((dataset) => {
|
||||
const isComplete = dataset.status === "complete";
|
||||
const targetPath = isComplete
|
||||
@@ -101,24 +101,17 @@ const DatasetsPage = () => {
|
||||
return (
|
||||
<li
|
||||
key={dataset.id}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
gap: 12,
|
||||
padding: "14px 16px",
|
||||
borderBottom: "1px solid rgba(0,0,0,0.06)",
|
||||
}}
|
||||
style={styles.datasetListItem}
|
||||
>
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<div style={{ fontWeight: 700, color: "#111827" }}>
|
||||
<div style={styles.datasetName}>
|
||||
{dataset.name || `Dataset #${dataset.id}`}
|
||||
</div>
|
||||
<div style={{ fontSize: 13, color: "#6b7280", marginTop: 4 }}>
|
||||
<div style={styles.datasetMeta}>
|
||||
ID #{dataset.id} • Status: {dataset.status || "unknown"}
|
||||
</div>
|
||||
{dataset.status_message && (
|
||||
<div style={{ fontSize: 13, color: "#6b7280", marginTop: 2 }}>
|
||||
<div style={styles.datasetMetaSecondary}>
|
||||
{dataset.status_message}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -6,11 +6,6 @@ import StatsStyling from "../styles/stats_styling";
|
||||
const API_BASE_URL = import.meta.env.VITE_BACKEND_URL
|
||||
|
||||
const styles = StatsStyling;
|
||||
const controlStyle = {
|
||||
width: "100%",
|
||||
maxWidth: "100%",
|
||||
boxSizing: "border-box" as const,
|
||||
};
|
||||
|
||||
const LoginPage = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -77,54 +72,44 @@ const LoginPage = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ ...styles.container, maxWidth: 560, padding: "48px 24px" }}>
|
||||
<div
|
||||
style={{
|
||||
...styles.card,
|
||||
padding: 28,
|
||||
background:
|
||||
"linear-gradient(180deg, rgba(255,255,255,1) 0%, rgba(248,250,255,1) 100%)",
|
||||
}}
|
||||
>
|
||||
<div style={{ marginBottom: 22, textAlign: "center" }}>
|
||||
<h1 style={{ margin: 0, color: "#111827", fontSize: 30, lineHeight: 1.1 }}>
|
||||
<div style={styles.containerAuth}>
|
||||
<div style={{ ...styles.card, ...styles.authCard }}>
|
||||
<div style={styles.headingBlock}>
|
||||
<h1 style={styles.headingXl}>
|
||||
{isRegisterMode ? "Create your account" : "Welcome back"}
|
||||
</h1>
|
||||
<p style={{ margin: "8px 0 0", color: "#6b7280", fontSize: 14 }}>
|
||||
<p style={styles.mutedText}>
|
||||
{isRegisterMode
|
||||
? "Register to start uploading and exploring your dataset insights."
|
||||
: "Sign in to continue to your analytics workspace."}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
style={{ display: "grid", gap: 12, maxWidth: 380, margin: "0 auto" }}
|
||||
>
|
||||
<form onSubmit={handleSubmit} style={styles.authForm}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Username"
|
||||
style={{ ...styles.input, ...controlStyle }}
|
||||
style={{ ...styles.input, ...styles.authControl }}
|
||||
value={username}
|
||||
onChange={(event) => setUsername(event.target.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
{isRegisterMode && (
|
||||
<input
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
style={{ ...styles.input, ...controlStyle }}
|
||||
value={email}
|
||||
onChange={(event) => setEmail(event.target.value)}
|
||||
required
|
||||
<input
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
style={{ ...styles.input, ...styles.authControl }}
|
||||
value={email}
|
||||
onChange={(event) => setEmail(event.target.value)}
|
||||
required
|
||||
/>
|
||||
)}
|
||||
|
||||
<input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
style={{ ...styles.input, ...controlStyle }}
|
||||
style={{ ...styles.input, ...styles.authControl }}
|
||||
value={password}
|
||||
onChange={(event) => setPassword(event.target.value)}
|
||||
required
|
||||
@@ -132,7 +117,7 @@ const LoginPage = () => {
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
style={{ ...styles.buttonPrimary, ...controlStyle, marginTop: 2 }}
|
||||
style={{ ...styles.buttonPrimary, ...styles.authControl, marginTop: 2 }}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading
|
||||
@@ -144,57 +129,24 @@ const LoginPage = () => {
|
||||
</form>
|
||||
|
||||
{error && (
|
||||
<p
|
||||
style={{
|
||||
color: "#b91c1c",
|
||||
margin: "12px auto 0",
|
||||
fontSize: 14,
|
||||
maxWidth: 380,
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
<p style={styles.authErrorText}>
|
||||
{error}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{info && (
|
||||
<p
|
||||
style={{
|
||||
color: "#166534",
|
||||
margin: "12px auto 0",
|
||||
fontSize: 14,
|
||||
maxWidth: 380,
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
<p style={styles.authInfoText}>
|
||||
{info}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div
|
||||
style={{
|
||||
marginTop: 16,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: 8,
|
||||
flexWrap: "wrap",
|
||||
}}
|
||||
>
|
||||
<span style={{ color: "#6b7280", fontSize: 14 }}>
|
||||
<div style={styles.authSwitchRow}>
|
||||
<span style={styles.authSwitchLabel}>
|
||||
{isRegisterMode ? "Already have an account?" : "New here?"}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
style={{
|
||||
border: "none",
|
||||
background: "transparent",
|
||||
color: "#2563eb",
|
||||
fontSize: 14,
|
||||
fontWeight: 600,
|
||||
cursor: "pointer",
|
||||
padding: 0,
|
||||
}}
|
||||
style={styles.authSwitchButton}
|
||||
onClick={() => {
|
||||
setError("");
|
||||
setInfo("");
|
||||
|
||||
@@ -174,11 +174,11 @@ return (
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style={{ fontSize: 13, color: "#6b7280" }}>Analytics Dashboard</div>
|
||||
<div style={{ fontSize: 13, color: "#6b7280" }}>Dataset #{datasetId ?? "-"}</div>
|
||||
<div style={styles.dashboardMeta}>Analytics Dashboard</div>
|
||||
<div style={styles.dashboardMeta}>Dataset #{datasetId ?? "-"}</div>
|
||||
</div>
|
||||
|
||||
<div style={{ ...styles.container, display: "flex", gap: 8, marginTop: 12 }}>
|
||||
<div style={{ ...styles.container, ...styles.tabsRow }}>
|
||||
<button
|
||||
onClick={() => setActiveView("summary")}
|
||||
style={activeView === "summary" ? styles.buttonPrimary : styles.buttonSecondary}
|
||||
|
||||
@@ -70,11 +70,11 @@ const UploadPage = () => {
|
||||
|
||||
return (
|
||||
<div style={styles.page}>
|
||||
<div style={{ ...styles.container, maxWidth: 1100 }}>
|
||||
<div style={styles.containerWide}>
|
||||
<div style={{ ...styles.card, ...styles.headerBar }}>
|
||||
<div>
|
||||
<h1 style={{ margin: 0, color: "#111827", fontSize: 28 }}>Upload Dataset</h1>
|
||||
<p style={{ margin: "8px 0 0", color: "#6b7280", fontSize: 14 }}>
|
||||
<h1 style={styles.sectionHeaderTitle}>Upload Dataset</h1>
|
||||
<p style={styles.sectionHeaderSubtitle}>
|
||||
Name your dataset, then upload posts and topic map files to generate analytics.
|
||||
</p>
|
||||
</div>
|
||||
@@ -96,10 +96,10 @@ const UploadPage = () => {
|
||||
}}
|
||||
>
|
||||
<div style={{ ...styles.card, gridColumn: "auto" }}>
|
||||
<h2 style={{ ...styles.sectionTitle, color: "#111827" }}>Dataset Name</h2>
|
||||
<h2 style={{ ...styles.sectionTitle, color: "#24292f" }}>Dataset Name</h2>
|
||||
<p style={styles.sectionSubtitle}>Use a clear label so you can identify this upload later.</p>
|
||||
<input
|
||||
style={{ ...styles.input, width: "100%", maxWidth: "100%", boxSizing: "border-box" }}
|
||||
style={{ ...styles.input, ...styles.inputFullWidth }}
|
||||
type="text"
|
||||
placeholder="Example: Cork Discussions - Jan 2026"
|
||||
value={datasetName}
|
||||
@@ -108,29 +108,29 @@ const UploadPage = () => {
|
||||
</div>
|
||||
|
||||
<div style={{ ...styles.card, gridColumn: "auto" }}>
|
||||
<h2 style={{ ...styles.sectionTitle, color: "#111827" }}>Posts File (.jsonl)</h2>
|
||||
<h2 style={{ ...styles.sectionTitle, color: "#24292f" }}>Posts File (.jsonl)</h2>
|
||||
<p style={styles.sectionSubtitle}>Upload the raw post records export.</p>
|
||||
<input
|
||||
style={{ ...styles.input, width: "100%", maxWidth: "100%", boxSizing: "border-box" }}
|
||||
style={{ ...styles.input, ...styles.inputFullWidth }}
|
||||
type="file"
|
||||
accept=".jsonl"
|
||||
onChange={(event) => setPostFile(event.target.files?.[0] ?? null)}
|
||||
/>
|
||||
<p style={{ margin: "10px 0 0", fontSize: 13, color: "#374151" }}>
|
||||
<p style={styles.subtleBodyText}>
|
||||
{postFile ? `Selected: ${postFile.name}` : "No file selected"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style={{ ...styles.card, gridColumn: "auto" }}>
|
||||
<h2 style={{ ...styles.sectionTitle, color: "#111827" }}>Topics File (.json)</h2>
|
||||
<h2 style={{ ...styles.sectionTitle, color: "#24292f" }}>Topics File (.json)</h2>
|
||||
<p style={styles.sectionSubtitle}>Upload your topic bucket mapping file.</p>
|
||||
<input
|
||||
style={{ ...styles.input, width: "100%", maxWidth: "100%", boxSizing: "border-box" }}
|
||||
style={{ ...styles.input, ...styles.inputFullWidth }}
|
||||
type="file"
|
||||
accept=".json"
|
||||
onChange={(event) => setTopicBucketFile(event.target.files?.[0] ?? null)}
|
||||
/>
|
||||
<p style={{ margin: "10px 0 0", fontSize: 13, color: "#374151" }}>
|
||||
<p style={styles.subtleBodyText}>
|
||||
{topicBucketFile ? `Selected: ${topicBucketFile.name}` : "No file selected"}
|
||||
</p>
|
||||
</div>
|
||||
@@ -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."}
|
||||
|
||||
42
frontend/src/styles/stats/appLayout.ts
Normal file
42
frontend/src/styles/stats/appLayout.ts
Normal file
@@ -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,
|
||||
},
|
||||
};
|
||||
92
frontend/src/styles/stats/auth.ts
Normal file
92
frontend/src/styles/stats/auth.ts
Normal file
@@ -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,
|
||||
},
|
||||
};
|
||||
42
frontend/src/styles/stats/cards.ts
Normal file
42
frontend/src/styles/stats/cards.ts
Normal file
@@ -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,
|
||||
},
|
||||
};
|
||||
55
frontend/src/styles/stats/datasets.ts
Normal file
55
frontend/src/styles/stats/datasets.ts
Normal file
@@ -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,
|
||||
},
|
||||
};
|
||||
51
frontend/src/styles/stats/emotional.ts
Normal file
51
frontend/src/styles/stats/emotional.ts
Normal file
@@ -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,
|
||||
},
|
||||
};
|
||||
34
frontend/src/styles/stats/feedback.ts
Normal file
34
frontend/src/styles/stats/feedback.ts
Normal file
@@ -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,
|
||||
},
|
||||
};
|
||||
157
frontend/src/styles/stats/foundations.ts
Normal file
157
frontend/src/styles/stats/foundations.ts
Normal file
@@ -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",
|
||||
},
|
||||
};
|
||||
28
frontend/src/styles/stats/modal.ts
Normal file
28
frontend/src/styles/stats/modal.ts
Normal file
@@ -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)",
|
||||
},
|
||||
};
|
||||
26
frontend/src/styles/stats/palette.ts
Normal file
26
frontend/src/styles/stats/palette.ts
Normal file
@@ -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;
|
||||
3
frontend/src/styles/stats/types.ts
Normal file
3
frontend/src/styles/stats/types.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import type { CSSProperties } from "react";
|
||||
|
||||
export type StyleMap = Record<string, CSSProperties>;
|
||||
@@ -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<string, CSSProperties> = {
|
||||
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;
|
||||
Reference in New Issue
Block a user