mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-11-14 08:17:26 +01:00
The UI was uneven: the home page looked finished, but app pages used three different display typefaces, the anonymize flow changed frame between steps, and the dashboard, explorer and admin screens each had their own controls, status colours and edge-case handling. Make every screen follow one system: shared page header, grid, buttons, filters and status colours; a repository picker and step trail on anonymize; a "What reviewers will see" preview of the actual reviewer page; consistent file headers and viewers for every file type; truthful admin charts and tables. Fold the accumulated override blocks back into style.css (22% smaller, no visual change). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
152 lines
15 KiB
HTML
152 lines
15 KiB
HTML
<div class="container page paper-page conference-page">
|
|
<div>
|
|
<div class="paper-crumbs">
|
|
<a href="/dashboard">My work</a> / <a href="/conferences">Conferences</a> / <span class="here">{{ conference?.conferenceID }}</span>
|
|
</div>
|
|
<div class="d-flex align-items-end flex-wrap" style="gap: 12px; justify-content: space-between;">
|
|
<div>
|
|
<div class="conf-title-row">
|
|
<h1 class="paper-page-title" v-text="conference?.name"></h1>
|
|
<span class="conf-status" v-if="conference?.status"><span class="status-dot" aria-hidden="true" :class="'status-' + conference?.status"></span><span v-text="fmt?.statusLabel(conference?.status)"></span></span>
|
|
</div>
|
|
<p class="paper-page-lede" v-if="conference?.url">
|
|
<a target="_blank" rel="noopener" v-text="conference?.url" :href="safeUrl((conference?.url))"></a>
|
|
</p>
|
|
</div>
|
|
<div class="d-flex align-items-center flex-wrap" style="gap: 10px;">
|
|
<a class="btn btn-outline-ink" :href="safeUrl("/conference/" + (conference?.conferenceID) + "/edit")"><svg class="ui-icon mr-1" aria-hidden="true"><use href="#ui-icon-edit"></use></svg> Edit conference</a>
|
|
</div>
|
|
</div>
|
|
<div class="paper-meta-rule">
|
|
<span>ID <b class="commit-hash" v-text="conference?.conferenceID"></b></span>
|
|
<span>Review window <b>{{ fmt?.date(conference?.startDate, 'mediumDate') }} – {{ fmt?.date(conference?.endDate, 'mediumDate') }}</b></span>
|
|
<span>Repositories <b>{{ fmt?.number(conference?.repositories?.length) }}</b></span>
|
|
<span>Plan <b v-text="conferencePlanName(conference?.plan?.planID)"></b></span>
|
|
<span v-if="conference?.price">Cost so far <b>{{ fmt?.money(conference?.price) }}</b></span>
|
|
</div>
|
|
|
|
<div class="conference-payment-status" v-if="billingNotice" role="status">
|
|
<p>{{ billingNotice }}</p>
|
|
<button type="button" class="btn btn-outline-ink" @click="refreshBilling()" :disabled="billingBusy">Check payment status</button>
|
|
</div>
|
|
<p v-if="billingError && conference?.status !== 'pending_payment'" role="alert">{{ billingError }}</p>
|
|
<section class="paper-settings-section" v-if="conference?.status === 'pending_payment'" aria-labelledby="conference-payment-heading">
|
|
<h2 id="conference-payment-heading">Activate your review package</h2>
|
|
<p>This conference is waiting for payment. Authors can join after payment is confirmed. Checkout returns to this conference. Authors within the package do not pay separately.</p>
|
|
<p v-if="billingLoading" role="status">Loading payment options…</p>
|
|
<p v-if="billingError" role="alert">{{ billingError }}</p>
|
|
<div v-for="plan in (billing?.plans || []).filter(p => p.id === conference?.plan?.planID)" :key="plan.id">
|
|
<p><strong>{{ plan.name }} package</strong>: {{ billingPrice(plan) }} for up to {{ plan.repositories }} repositories and {{ billingBytes(plan.storage) }} of source files during the review window. Taxes, if applicable, appear at checkout.</p>
|
|
<button type="button" class="btn btn-primary" @click="checkout(plan.id, conference.conferenceID)" :disabled="billingBusy || billingPending || billing?.checkoutEnabled === false">{{ billingBusy ? 'Please wait…' : 'Pay for review package' }}</button>
|
|
</div>
|
|
<p><a href="/profile#settings-billing">Manage invoices</a></p>
|
|
</section>
|
|
<div class="dashboard-meta conf-view-meta">
|
|
<div class="dashboard-view-tabs" role="group" aria-label="Repository view">
|
|
<button type="button" :aria-pressed="confView() === 'current'" @click="setConfView('current')" title="Every repository except removed links">Current</button>
|
|
<button type="button" v-if="confCount('attention') > 0 || confView() === 'attention'" :aria-pressed="confView() === 'attention'" @click="setConfView('attention')" :aria-label="'Needs attention ' + confCount('attention')"><span class="dashboard-attention-prefix">Needs </span><span>attention</span> <span class="attention-count" :class="{'has-attention': confCount('attention') > 0}">{{ fmt?.number(confCount('attention')) }}</span></button>
|
|
<button type="button" v-if="confCount('removed') > 0 || confView() === 'removed'" :aria-pressed="confView() === 'removed'" @click="setConfView('removed')" title="Links reviewers can no longer open">Removed <span class="attention-count">{{ fmt?.number(confCount('removed')) }}</span></button>
|
|
</div>
|
|
<span class="dashboard-count" role="status">{{ search.trim() ? fmt?.number(filteredRepositories?.length || 0) + ' shown' : fmt?.number(filteredRepositories?.length || 0) + ((filteredRepositories?.length || 0) === 1 ? ' repository' : ' repositories') }}</span>
|
|
</div>
|
|
<form class="w-100 conf-repo-toolbar" aria-label="Filter repositories" accept-charset="UTF-8" v-form="viewState" @submit.prevent="submitForm($event, () => { $event.preventDefault() })">
|
|
<div class="search-wrap">
|
|
<input type="search" id="search" class="form-control" aria-label="Search repositories" placeholder="Search repositories…" autocomplete="off" v-field="{ state: viewState, set: value => { search = value }, value: search, form: null, options: {} }">
|
|
</div>
|
|
<div class="dashboard-filter-controls">
|
|
<div class="dropdown">
|
|
<button class="btn dropdown-toggle" type="button" id="dropdownSort" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
|
<span class="filter-btn-label">Sort</span>
|
|
<span class="filter-btn-value">{{ ({'-anonymizeDate': 'Anonymize date', repoId: 'ID', '-status': 'Status', '-pageView': 'Views'})[orderBy] || 'Custom' }}</span>
|
|
</button>
|
|
<div class="dropdown-menu" aria-labelledby="dropdownSort">
|
|
<h6 class="dropdown-header">Order by</h6>
|
|
<div class="form-check dropdown-item">
|
|
<input class="form-check-input" type="radio" name="sort" id="anonymizeDate" value="-anonymizeDate" v-field="{ state: viewState, set: value => { orderBy = value }, value: orderBy, form: null, options: {} }">
|
|
<label class="form-check-label" for="anonymizeDate">Anonymize date</label>
|
|
</div>
|
|
<div class="form-check dropdown-item">
|
|
<input class="form-check-input" type="radio" name="sort" id="sortID" value="repoId" v-field="{ state: viewState, set: value => { orderBy = value }, value: orderBy, form: null, options: {} }">
|
|
<label class="form-check-label" for="sortID">ID</label>
|
|
</div>
|
|
<div class="form-check dropdown-item">
|
|
<input class="form-check-input" type="radio" name="sort" id="sortStatus" value="-status" v-field="{ state: viewState, set: value => { orderBy = value }, value: orderBy, form: null, options: {} }">
|
|
<label class="form-check-label" for="sortStatus">Status</label>
|
|
</div>
|
|
<div class="form-check dropdown-item">
|
|
<input class="form-check-input" type="radio" name="sort" id="sortViews" value="-pageView" v-field="{ state: viewState, set: value => { orderBy = value }, value: orderBy, form: null, options: {} }">
|
|
<label class="form-check-label" for="sortViews">Views</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
|
|
<div class="dashboard-page conf-rows"><div class="paper-table paper-table-dashboard paper-table-conf w-100" role="table" aria-label="Repositories" :class="{'is-removed-view': confView() === 'removed'}">
|
|
<div class="paper-table-head" role="row">
|
|
<div role="columnheader">Repository</div>
|
|
<div role="columnheader">Status</div>
|
|
<div role="columnheader" class="num">Views</div>
|
|
<div role="columnheader">Expires</div>
|
|
<div role="columnheader"><span class="sr-only">Actions</span></div>
|
|
</div>
|
|
<div class="paper-table-row" role="row" v-for="(repo, index) in filteredRepositories" :key="repo?.repoId" :class="{'repo-inactive': repo?.status == 'expired' || repo?.status == 'removed', 'repo-error': repo?.status == 'error'}">
|
|
<div class="cell-anon" role="cell">
|
|
<span class="project-type-icon" role="img" aria-label="Repository" title="Repository"><svg class="project-type-svg" aria-hidden="true"><use href="#project-type-repo"></use></svg></span>
|
|
<div class="anon-text">
|
|
<div class="anon-title">
|
|
<a class="repo-name" :title="repo?.source?.fullName" :href="safeUrl("/r/" + (repo?.repoId) + "/")"><template v-if="(repo?.source?.fullName || '').indexOf('/') > 0"><span class="rn-owner">{{ repo.source.fullName.slice(0, repo.source.fullName.indexOf('/') + 1) }}</span><wbr><span class="rn-repo">{{ repo.source.fullName.slice(repo.source.fullName.indexOf('/') + 1) }}</span></template><template v-else>{{ repo?.source?.fullName || repo?.repoId }}</template></a>
|
|
</div>
|
|
<div class="anon-sub">
|
|
<code class="anonymous-id" :title="repo?.repoId"><template v-if="(repo?.repoId || '').length > 14"><span class="id-head">{{ repo.repoId.slice(0, -6) }}</span><span class="id-tail">{{ repo.repoId.slice(-6) }}</span></template><template v-else>{{ repo?.repoId }}</template></code>
|
|
<span class="commit-hash" v-if="repo?.source?.commit" :title="repo.source.commit">{{ repo.source.commit.substring(0, 7) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="dashboard-row-meta" role="presentation">
|
|
<div class="cell-status" role="cell">
|
|
<div class="status-line">
|
|
<span class="status-dot" aria-hidden="true" :class="'status-' + (repo?.status == 'error' ? 'error' : repo?.status == 'ready' ? 'ready' : (repo?.status == 'expired' || repo?.status == 'removed') ? repo?.status : 'progress')"></span>
|
|
<span class="status-word" v-text="fmt?.statusLabel(repo?.status)"></span>
|
|
</div>
|
|
<div class="status-sub status-sub-error" v-if="repo?.status == 'error' && repo?.statusMessage" v-text="fmt?.statusMsg(repo?.statusMessage)" :title="repo?.statusMessage"></div>
|
|
<a class="project-attention-action" v-if="repo?.status == 'error'" :href="safeUrl('/status/' + repo?.repoId)">View status</a>
|
|
<button type="button" class="project-attention-action" v-if="repo?.status == 'expired'" @click.stop="extendRepo(repo)" :disabled="extendingId === repo?.repoId" title="Reviewers can no longer open this link">Extend 6 months</button>
|
|
<div class="status-sub status-sub-date" v-if="repo?.anonymizeDate && repo?.status != 'error' && repo?.status != 'expired'">anonymized {{ fmt?.humanTime(repo?.anonymizeDate) }}</div>
|
|
</div>
|
|
<div class="cell-views num" role="cell" :data-unit="(repo?.pageView || 0) === 1 ? 'view' : 'views'" :aria-label="fmt?.number(repo?.pageView || 0) + ((repo?.pageView || 0) === 1 ? ' view' : ' views')"><svg class="project-type-svg mobile-meta-icon" aria-hidden="true"><use href="#project-icon-eye"></use></svg><span>{{ fmt?.number(repo?.pageView || 0) }}</span></div>
|
|
<div class="cell-expires" role="cell">
|
|
<svg class="project-type-svg mobile-meta-icon" aria-hidden="true"><use href="#project-icon-clock"></use></svg>
|
|
<span class="expires-never" v-if="repo?.status == 'ready' && (repo?.options?.expirationMode === 'never' || !repo?.options?.expirationDate)">No expiry</span>
|
|
<span v-if="repo?.status == 'ready' && repo?.options?.expirationMode !== 'never' && repo?.options?.expirationDate" v-text="(fmt?.humanTime(repo?.options?.expirationDate) || '').replace(/^on /, '')"></span>
|
|
<span class="expires-past" v-if="repo?.status == 'expired'"><span class="expires-past-prefix">Expired </span>{{ fmt?.shortDate(repo?.options?.expirationDate) }}</span>
|
|
<span class="empty-dash" aria-label="Not applicable" v-if="repo?.status != 'ready' && repo?.status != 'expired'">—</span>
|
|
</div>
|
|
</div>
|
|
<div class="cell-actions" role="cell">
|
|
<button type="button" class="copy-url" v-if="repo?.status != 'expired' && repo?.status != 'removed'" @click.stop="copyRepoLink(repo)" :disabled="!!copyingId" :title="'Copy link: /r/' + repo?.repoId + '/'" :aria-label="'Copy anonymous link for ' + repo?.repoId"><span class="sr-only" aria-live="polite">{{ copyingId === repo?.repoId ? 'Link copied' : '' }}</span><svg class="project-type-svg" aria-hidden="true"><use :href="copyingId === repo?.repoId ? '#ui-icon-check' : '#project-icon-copy'"></use></svg><span class="copy-url-label" aria-hidden="true">{{ copyingId === repo?.repoId ? 'Copied' : 'Copy link' }}</span></button>
|
|
<a class="row-edit row-open" v-if="repo?.status != 'expired' && repo?.status != 'removed'" :href="safeUrl("/r/" + (repo?.repoId) + "/")" target="_blank" rel="noopener" title="Open what reviewers see" :aria-label="'Open anonymous page for ' + repo?.repoId"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-external-link"></use></svg></a>
|
|
<div class="dropdown">
|
|
<button class="btn btn-icon-dots" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" :aria-label=""Actions for " + (repo?.repoId)">
|
|
<svg class="project-type-svg" aria-hidden="true"><use href="#project-icon-more"></use></svg>
|
|
</button>
|
|
<div class="dropdown-menu dropdown-menu-right">
|
|
<a class="dropdown-item" :href="safeUrl("/r/" + (repo?.repoId) + "/")">View</a>
|
|
<a class="dropdown-item" :href="safeUrl("https://github.com/" + (repo?.source?.fullName) + "/")" target="_blank" rel="noopener">Open source on GitHub</a>
|
|
<a class="dropdown-item" :href="safeUrl("/anonymize/" + (repo?.repoId))">Edit</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="paper-table-empty" v-if="filteredRepositories?.length == 0">
|
|
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-inbox"></use></svg>
|
|
<span v-if="!conference?.repositories?.length">No repository has been submitted to this conference yet.</span>
|
|
<span v-if="conference?.repositories?.length">Nothing matches the current filters.</span>
|
|
<div class="paper-table-empty-actions" v-if="conference?.repositories?.length">
|
|
<button type="button" class="btn btn-outline-ink" @click="search = ''; setConfView('current')">Clear filters</button>
|
|
</div>
|
|
</div>
|
|
</div></div>
|
|
</div>
|
|
</div>
|