feat: build out project dashboard (#2)
Node.js CI / Lint and Test (push) Successful in 57s

### Explanation

This creates an interactive product directory to help potential consumers discover our works.

### Issue

_No response_

### Attestations

- [x] I have read and agree to the [Code of Conduct](https://docs.nhcarrigan.com/community/coc/)
- [x] I have read and agree to the [Community Guidelines](https://docs.nhcarrigan.com/community/guide/).
- [x] My contribution complies with the [Contributor Covenant](https://docs.nhcarrigan.com/dev/covenant/).

### Dependencies

- [x] I have pinned the dependencies to a specific patch version.

### Style

- [x] I have run the linter and resolved any errors.
- [x] My pull request uses an appropriate title, matching the conventional commit standards.
- [x] My scope of feat/fix/chore/etc. correctly matches the nature of changes in my pull request.

### Tests

- [ ] My contribution adds new code, and I have added tests to cover it.
- [ ] My contribution modifies existing code, and I have updated the tests to reflect these changes.
- [ ] All new and existing tests pass locally with my changes.
- [ ] Code coverage remains at or above the configured threshold.

### Documentation

_No response_

### Versioning

_No response_

Reviewed-on: #2
Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com>
Co-committed-by: Naomi Carrigan <commits@nhcarrigan.com>
This commit was merged in pull request #2.
This commit is contained in:
2025-07-04 20:05:20 -07:00
committed by Naomi Carrigan
parent 6b19de55f2
commit 6e8c048e25
17 changed files with 1190 additions and 22 deletions
+150
View File
@@ -0,0 +1,150 @@
<h1>Products</h1>
<img
src="https://cdn.nhcarrigan.com/new-avatars/hikari-thinking-full.png"
alt="Hikari"
height="250"
/>
<p>Excellent! What sort of product are you looking for?</p>
<div class="row">
<button
class="btn"
(click)="selectCategory('community')"
[disabled]="view === 'community' ? true : false"
>
Community Tooling and Integrations
</button>
<button
class="btn"
(click)="selectCategory('websites')"
[disabled]="view === 'websites' ? true : false"
>
Websites and APIs
</button>
<button
class="btn"
(click)="selectCategory('apps')"
[disabled]="view === 'apps' ? true : false"
>
Apps and Games
</button>
<button
class="btn"
(click)="selectCategory('all')"
[disabled]="view === 'all' ? true : false"
>
Show Me Everything!
</button>
</div>
<p>And would you like to apply a filter?</p>
<div class="row">
<button class="btn" (click)="toggleFilter('wip')">
<span *ngIf="filters.wip">Hide</span
><span *ngIf="!filters.wip">Show</span> WIP
</button>
<button class="btn" (click)="toggleFilter('prod')">
<span *ngIf="filters.prod">Hide</span
><span *ngIf="!filters.prod">Show</span> Production
</button>
<button class="btn" (click)="toggleFilter('paid')">
<span *ngIf="filters.paid">Hide</span
><span *ngIf="!filters.paid">Show</span> Paid
</button>
<button class="btn" (click)="toggleFilter('free')">
<span *ngIf="filters.free">Hide</span
><span *ngIf="!filters.free">Show</span> Free
</button>
</div>
<hr />
<p *ngIf="products.length === 0">
Oh dear, it appears there are no products in this category yet! Please check
back later.
</p>
<div id="products">
<div *ngFor="let product of products">
<!-- Render as <a> if product has a URL -->
<a
*ngIf="product.url"
[class]="product.wip ? 'product wip' : 'product'"
[href]="product.url"
target="_blank"
>
<h2 class="title">{{ product.name }}</h2>
<img
class="logo"
[src]="product.avatar ?? 'https://cdn.nhcarrigan.com/logo.png'"
alt="{{ product.name }} Logo"
/>
<p class="description">{{ product.description }}</p>
<div class="icons">
<i
title="Under construction"
*ngIf="product.wip"
class="fa-solid fa-wrench"
style="color: rgb(141, 23, 23)"
></i>
<i
title="Production Ready"
*ngIf="!product.wip"
class="fa-solid fa-check"
style="color: rgb(31, 117, 19)"
></i>
<i
title="Requires Subscription"
*ngIf="product.premium"
class="fa-solid fa-money-bill-1-wave"
style="color: rgb(145, 129, 40)"
></i>
<i
title="Free to Use"
*ngIf="!product.premium"
class="fa-solid fa-piggy-bank"
style="color: rgb(116, 37, 206)"
></i>
</div>
</a>
<!-- Render as <div> if no URL -->
<div *ngIf="!product.url" [class]="product.wip ? 'product wip' : 'product'">
<h2 class="title">{{ product.name }}</h2>
<img
class="logo"
[src]="product.avatar ?? 'https://cdn.nhcarrigan.com/logo.png'"
alt="{{ product.name }} Logo"
/>
<p class="description">{{ product.description }}</p>
<div *ngIf="product.wip || product.premium" class="icons">
<i
title="Under construction"
*ngIf="product.wip"
class="fa-solid fa-wrench"
style="color: rgb(141, 23, 23)"
></i>
<i
title="Production Ready"
*ngIf="!product.wip"
class="fa-solid fa-check"
style="color: rgb(31, 117, 19)"
></i>
<i
title="Requires Subscription"
*ngIf="product.premium"
class="fa-solid fa-money-bill-1-wave"
style="color: rgb(145, 129, 40)"
></i>
<i
title="Free to Use"
*ngIf="!product.premium"
class="fa-solid fa-piggy-bank"
style="color: rgb(116, 37, 206)"
></i>
</div>
</div>
</div>
</div>
<hr />
<a
href="https://forms.nhcarrigan.com/form/XRlQjeu8CbMrTA-v0IPOxlUPEPitLKXTWg70UUCIORA"
target="_blank"
class="btn"
>I want something custom...</a
>