Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@ dist-ssr
docs/superpowers/
.worktrees/

# Raw local data dumps (may contain sensitive info, never commit)
data/

# Editor directories and files
.vscode/*
!.vscode/extensions.json
Expand All @@ -28,3 +25,4 @@ data/
*.njsproj
*.sln
*.sw?
/data/
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400;0,500;0,600;0,700;1,400&display=swap"
href="https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400;0,500;0,600;0,700;1,400&family=Cardo:ital,wght@0,400;0,700;1,400&display=swap"
rel="stylesheet"
/>
<title>Powered by Sefaria Dashboard</title>
Expand Down
16 changes: 10 additions & 6 deletions src/Title.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import developersLogo from './assets/developers_logo.png'

function Title() {
return (
<header style={{ textAlign: 'center' }}>
<header className="app-header">
<h1>Powered by Sefaria Dashboard</h1>
<a href="https://developers.sefaria.org" target="_blank" rel="noreferrer">
<img src={developersLogo} alt="Sefaria Developers logo" />
</a>
<div className="app-subheader-block">
<p className="app-subheader" lang="he" dir="rtl">
"אי אפשר לבית מדרש בלא חידוש"
</p>
<p className="app-subheader-desc">
The center of all projects, websites, and apps created using Sefaria's open-source data and
technology
</p>
</div>
</header>
)
}
Expand Down
38 changes: 20 additions & 18 deletions src/components/Controls.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,26 @@ function Controls({
}) {
return (
<div className="dashboard-controls">
<input
type="text"
className="search-input"
placeholder="Search projects..."
value={searchText}
onChange={(event) => onSearchChange(event.target.value)}
/>
<select
value={selectedCategory}
onChange={(event) => onCategoryChange(event.target.value)}
>
<option value="All">All categories</option>
{categories.map((category) => (
<option key={category} value={category}>
{category}
</option>
))}
</select>
<div className="dashboard-controls-row">
<input
type="text"
className="search-input"
placeholder="Search by project or description"
value={searchText}
onChange={(event) => onSearchChange(event.target.value)}
/>
<select
value={selectedCategory}
onChange={(event) => onCategoryChange(event.target.value)}
>
<option value="All">All categories</option>
{categories.map((category) => (
<option key={category} value={category}>
{category}
</option>
))}
</select>
</div>
<span className="project-count">{count} projects</span>
</div>
)
Expand Down
2 changes: 1 addition & 1 deletion src/components/charts/SubmissionsTrendChart.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ function SubmissionsTrendChart({ data }) {
return (
<BarChart
data={data}
title="Submissions, past 12 months"
title="Submissions since August 2025"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!

dataKey="count"
categoryKey="month"
barName="Submissions"
Comment thread
saengel marked this conversation as resolved.
Expand Down
2 changes: 1 addition & 1 deletion src/components/charts/VibeCodedTrendChart.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import LineChart from './types/LineChart.jsx'
import { VIBE_CODED_SERIES } from '../../utils/submissionsTrend.js'

const VIBE_CODED_COLORS = {
'Not vibe-coded': 'var(--chart-blue)',
'Vibe-coded': 'var(--chart-orange)',
'Not vibe-coded': 'var(--chart-blue)',
}

function VibeCodedTrendChart({ data }) {
Expand Down
14 changes: 13 additions & 1 deletion src/components/charts/types/LineChart.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,19 @@ function LineChart({ data, series, title, description, height = 360 }) {
<XAxis dataKey="month" />
<YAxis allowDecimals={false} />
<Tooltip />
<Legend />
{/* Legend's default itemSorter is 'value', which alphabetizes entries by
name — overriding the order series/<Line> below are declared in (e.g.
"Not vibe-coded" would sort before "Vibe-coded"). itemSorter must be a
function (or a string key) — Recharts calls it unconditionally, so
passing null risks breaking rather than disabling the sort. Sort by
each item's index in `series` instead, to preserve declaration order;
unmatched items (shouldn't normally happen) go last. */}
<Legend
itemSorter={(item) => {
const index = series.findIndex(({ name }) => name === item.value)
return index === -1 ? series.length : index
}}
/>
{series.map(({ key, name, color }) => (
<Line
key={key}
Expand Down
2 changes: 1 addition & 1 deletion src/components/charts/types/PieChart.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ function PieChart({ data, dataKey, nameKey, title, colorForSlice, height = 360,
{data.length === 0 ? (
<p>Data unavailable.</p>
) : (
<ResponsiveContainer width="100%" height={height}>
<ResponsiveContainer width="100%" height={height} className="pie-chart">
<RechartsPieChart>
<Pie
data={data}
Expand Down
15 changes: 10 additions & 5 deletions src/data/fetchProjects.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
import { getCategories } from '../utils/categories.js'

const API_URL = 'https://www.sefaria.org/api/powered-by'
const PROD_API_URL = 'https://www.sefaria.org/api/powered-by'

export async function fetchProjects() {
const response = await fetch(API_URL)
async function fetchProjectList(url) {
const response = await fetch(url)

if (!response.ok) {
throw new Error(`Failed to fetch projects: ${response.status}`)
throw new Error(`Failed to fetch projects from ${url}: ${response.status}`)
}

const data = await response.json()

return data.projects
}

export async function fetchProjects() {
const projects = await fetchProjectList(PROD_API_URL)

return projects
.filter((project) => project.is_published && project.consent_to_display)
.map((project) => ({
...project,
Expand Down
77 changes: 63 additions & 14 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,32 @@ p {
margin: 0;
}

.app-header {
text-align: center;
}

.app-subheader-block {
margin-top: 40px;
padding: 24px;
background: var(--accent-bg);
border-radius: 8px;
display: inline-block;
}

.app-subheader {
font-family: 'Cardo', var(--heading);
font-size: 28px;
color: var(--text-h);
margin: 0 0 8px;
}

.app-subheader-desc {
margin: 0 auto;
color: var(--text);
font-size: 20px;
line-height: 145%;
}
Comment thread
Copilot marked this conversation as resolved.

code,
.counter {
font-family: var(--mono);
Expand All @@ -104,23 +130,22 @@ code {
}

.dashboard-controls {
display: flex;
flex-direction: column;
gap: 8px;
margin: 24px 0;
padding: 0 24px;
}

.dashboard-controls-row {
display: flex;
gap: 12px;
align-items: center;
justify-content: flex-start;
flex-wrap: wrap;
margin: 24px 0;
/* Anchors the row's left edge under the Developers logo's left edge.
The logo (646px, centered in the full-width header) and this row (inside
the sidebar-offset tab-content) live in different coordinate spaces, so
matching them means re-deriving the logo's position here: half the
leftover viewport width outside the logo, minus the sidebar's full
rendered width (220px content + 24px padding + 1px border-right). */
margin-left: max(0px, calc((100vw - 646px) / 2 - 245px));
}

.dashboard-controls input,
.dashboard-controls select {
flex: 1;
font: inherit;
padding: 8px 12px;
border: 1px solid var(--border);
Expand All @@ -129,13 +154,18 @@ code {
color: var(--text-h);
}

.search-input {
width: 280px;
.dashboard-controls select {
appearance: none;
padding-right: 32px;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6' fill='none'%3E%3Cpath d='M1 1L5 5L9 1' stroke='%23555' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 14px center;
}

.project-count {
color: var(--text);
font-size: 14px;
margin-left: 6px;
}

.project-grid {
Expand Down Expand Up @@ -242,7 +272,7 @@ code {
padding: 2px 8px;
border-radius: 4px;
background: color-mix(in srgb, var(--category-color, var(--accent)) 14%, var(--bg));
color: color-mix(in srgb, var(--category-color, var(--accent)) 70%, var(--text-h));
color: #000;
border: 1px solid color-mix(in srgb, var(--category-color, var(--accent)) 40%, var(--bg));
}

Expand All @@ -255,7 +285,22 @@ code {
/* fills whatever vertical space the back face leaves after the category tags */
flex: 1;
min-height: 0;
overflow-y: auto;
overflow-y: scroll;
scrollbar-color: color-mix(in srgb, var(--text) 55%, transparent) transparent;
scrollbar-width: thin;
}

.project-card-desc::-webkit-scrollbar {
width: 8px;
}

.project-card-desc::-webkit-scrollbar-thumb {
background-color: color-mix(in srgb, var(--text) 55%, transparent);
border-radius: 4px;
}

.project-card-desc::-webkit-scrollbar-track {
background: transparent;
}

.app-body {
Expand Down Expand Up @@ -310,3 +355,7 @@ code {
flex-direction: column;
gap: 48px;
}

.pie-chart .recharts-legend-item {
margin-right: 20px !important;
}
Loading
Loading