setup workos

This commit was merged in pull request #10.
This commit is contained in:
2026-05-02 20:57:09 -04:00
committed by Richie
parent de9e59b5f4
commit 7f2b388e7a
20 changed files with 1286 additions and 142 deletions
+36
View File
@@ -0,0 +1,36 @@
{% extends "base.html" %}
{% block title %}Admin Settings{% endblock %}
{% block body %}
<main class="shell">
<section class="page-heading stacked-heading">
<div>
<h1>Admin settings</h1>
<p>Admin-only operational controls for the Nornsight workspace.</p>
</div>
</section>
<section class="admin-card">
<h2>WorkOS-managed access</h2>
<p>
Invitations, Google access, and role assignments are managed in the WorkOS dashboard.
This page confirms that app-level admin gating is active.
</p>
<dl class="admin-meta">
<div>
<dt>Workspace organization</dt>
<dd><code>{{ organization_id }}</code></dd>
</div>
<div>
<dt>Current administrator</dt>
<dd>{{ current_user_email }}</dd>
</div>
</dl>
<div class="admin-actions">
<a href="/dashboard">Return to dashboard</a>
<a href="https://dashboard.workos.com/" rel="noreferrer" target="_blank">Open WorkOS dashboard</a>
</div>
</section>
</main>
{% endblock %}
+15 -5
View File
@@ -15,23 +15,33 @@
</a>
{% if show_primary_nav|default(true) %}
<nav class="primary-nav" aria-label="Primary">
<a href="/">Issues</a>
{% if is_authenticated|default(false) %}
<a href="/dashboard">Dashboard</a>
<a href="/legislators">Legislators</a>
<a href="/compare">Compare</a>
{% if is_admin|default(false) %}
<a href="/admin">Admin</a>
{% endif %}
{% else %}
<a href="/">Overview</a>
{% endif %}
</nav>
{% endif %}
<nav class="account-nav" aria-label="Account">
<a href="#" aria-disabled="true">Help</a>
{% if is_authenticated|default(true) %}
{% if is_authenticated|default(false) %}
<details class="account-menu">
<summary>My account</summary>
<summary>{{ current_user_name or "My account" }}</summary>
<div class="account-menu-panel">
<span class="account-email">{{ current_user_email }}</span>
<a href="#" aria-disabled="true">Account settings</a>
<a class="sign-out" href="/logout">Sign out</a>
<form action="/logout" method="post">
<button class="sign-out" type="submit">Sign out</button>
</form>
</div>
</details>
{% else %}
<a class="sign-in" href="/login">Sign in</a>
<a class="sign-in" href="/login?next=/dashboard">Sign in</a>
{% endif %}
</nav>
</header>
+1
View File
@@ -10,6 +10,7 @@
<p>US legislative accountability · precomputed legislator topic scores{% if latest_score_year %} through {{ latest_score_year }}{% endif %}</p>
</div>
<div class="heading-actions">
<span>{{ current_user_email }}</span>
<a href="#" aria-disabled="true">Methodology</a>
<a href="#" aria-disabled="true">Data sources</a>
<span>Last updated: {{ last_updated.strftime("%b %Y") if last_updated else "Unavailable" }}</span>
+59
View File
@@ -0,0 +1,59 @@
{% extends "base.html" %}
{% block title %}Nornsight | Legislative Accountability{% endblock %}
{% block body %}
<main class="shell home-shell">
{% if auth_error %}
<div class="notice auth-notice">Authentication failed. Try signing in again.</div>
{% endif %}
<section class="hero-panel">
<div class="hero-copy">
<p class="eyebrow">Invite-only access</p>
<h1>Track legislative behavior with role-aware access and shared WorkOS sign-in.</h1>
<p class="hero-text">
Nornsight turns roll-call data into issue-level accountability views for your invited team.
Use the public home page as the front door, then move signed-in users into the dashboard,
legislator search, and comparison tools.
</p>
<div class="hero-actions">
{% if is_authenticated %}
<a class="hero-primary" href="/dashboard">Open dashboard</a>
{% if is_admin %}
<a class="hero-secondary" href="/admin">Admin settings</a>
{% endif %}
{% else %}
<a class="hero-primary" href="/login?next=/dashboard">Sign in</a>
<a class="hero-secondary" href="#access-model">How access works</a>
{% endif %}
</div>
</div>
<aside class="hero-card">
<h2>Launch access model</h2>
<ul>
<li>Public landing page at <code>/</code></li>
<li>Invite-only AuthKit login with Email + Password and Google</li>
<li><code>viewer</code> role for dashboard, legislators, and compare</li>
<li><code>admin</code> role for settings and account administration</li>
</ul>
</aside>
</section>
<section id="access-model" class="home-grid">
<article class="home-card">
<h2>For invited users</h2>
<p>View the dashboard, inspect legislator profiles, and compare issue scoring without sharing a local password.</p>
</article>
<article class="home-card">
<h2>For admins</h2>
<p>Manage invitations and role assignments in WorkOS while the app enforces role-based route access.</p>
</article>
<article class="home-card">
<h2>For rollout</h2>
<p>Authentication is centralized, sessions are sealed, and the old hard-coded admin login is removed.</p>
</article>
</section>
</main>
{% endblock %}
+1 -1
View File
@@ -2,7 +2,7 @@
<header>
<h2>Score history{% if selected_issue_label %} — {{ selected_issue_label }}{% endif %}</h2>
<a href="{{ build_url(request, compare=[]) }}"
hx-get="/partials/dashboard{{ build_url(request, compare=[])|replace('/', '', 1) }}"
hx-get="{{ build_dashboard_partial_url(request, compare=[]) }}"
hx-target="#dashboard-body"
hx-push-url="{{ build_url(request, compare=[]) }}">Clear comparison</a>
</header>
@@ -3,17 +3,17 @@
<div class="chamber-card">
<a class="segment {{ 'active' if chamber == 'house' else '' }}"
href="{{ build_url(request, chamber='house') }}"
hx-get="/partials/dashboard{{ build_url(request, chamber='house')|replace('/', '', 1) }}"
hx-get="{{ build_dashboard_partial_url(request, chamber='house') }}"
hx-target="#dashboard-body"
hx-push-url="{{ build_url(request, chamber='house') }}">House</a>
<a class="segment {{ 'active' if chamber == 'senate' else '' }}"
href="{{ build_url(request, chamber='senate') }}"
hx-get="/partials/dashboard{{ build_url(request, chamber='senate')|replace('/', '', 1) }}"
hx-get="{{ build_dashboard_partial_url(request, chamber='senate') }}"
hx-target="#dashboard-body"
hx-push-url="{{ build_url(request, chamber='senate') }}">Senate</a>
<a class="segment {{ 'active' if chamber == 'all' else '' }}"
href="{{ build_url(request, chamber='all') }}"
hx-get="/partials/dashboard{{ build_url(request, chamber='all')|replace('/', '', 1) }}"
hx-get="{{ build_dashboard_partial_url(request, chamber='all') }}"
hx-target="#dashboard-body"
hx-push-url="{{ build_url(request, chamber='all') }}">All</a>
</div>
@@ -2,10 +2,10 @@
<h2>Issue filters</h2>
<form class="issue-form"
method="get"
action="/"
hx-get="/"
action="/dashboard"
hx-get="/partials/dashboard"
hx-target="#dashboard-body"
hx-push-url="true">
hx-push-url="/dashboard">
<input type="hidden" name="chamber" value="{{ chamber }}">
{% if congress %}
<input type="hidden" name="congress" value="{{ congress }}">
@@ -17,7 +17,7 @@
<span class="chip">
{{ issue }}
<a href="{{ build_url(request, issues=issues[:loop.index0] + issues[loop.index:]) }}"
hx-get="/partials/dashboard{{ build_url(request, issues=issues[:loop.index0] + issues[loop.index:])|replace('/', '', 1) }}"
hx-get="{{ build_dashboard_partial_url(request, issues=issues[:loop.index0] + issues[loop.index:]) }}"
hx-target="#dashboard-body"
hx-push-url="{{ build_url(request, issues=issues[:loop.index0] + issues[loop.index:]) }}"
aria-label="Remove {{ issue }}">×</a>
@@ -36,7 +36,7 @@
{% for suggestion in suggestions %}
{% if suggestion not in issues %}
<a href="{{ build_url(request, issues=issues + [suggestion]) }}"
hx-get="/partials/dashboard{{ build_url(request, issues=issues + [suggestion])|replace('/', '', 1) }}"
hx-get="{{ build_dashboard_partial_url(request, issues=issues + [suggestion]) }}"
hx-target="#dashboard-body"
hx-push-url="{{ build_url(request, issues=issues + [suggestion]) }}">{{ suggestion }}</a>
{% endif %}
@@ -10,7 +10,7 @@
{% set next_compare = toggle_compare(compare, row.legislator_id) %}
<li class="{{ 'selected' if row.legislator_id in compare else '' }}">
<a href="{{ build_url(request, compare=next_compare) }}"
hx-get="/partials/dashboard{{ build_url(request, compare=next_compare)|replace('/', '', 1) }}"
hx-get="{{ build_dashboard_partial_url(request, compare=next_compare) }}"
hx-target="#dashboard-body"
hx-push-url="{{ build_url(request, compare=next_compare) }}">
<span class="rank">{{ loop.index }}</span>
@@ -40,7 +40,7 @@
{% set next_compare = toggle_compare(compare, row.legislator_id) %}
<li class="{{ 'selected' if row.legislator_id in compare else '' }}">
<a href="{{ build_url(request, compare=next_compare) }}"
hx-get="/partials/dashboard{{ build_url(request, compare=next_compare)|replace('/', '', 1) }}"
hx-get="{{ build_dashboard_partial_url(request, compare=next_compare) }}"
hx-target="#dashboard-body"
hx-push-url="{{ build_url(request, compare=next_compare) }}">
<span class="rank">{{ loop.index }}</span>