mirror of
https://github.com/Lissy93/personal-security-checklist.git
synced 2025-01-26 22:26:58 -05:00
Extracts table into own component, starts Progress component, adds icons to nav
This commit is contained in:
parent
7a1801cc85
commit
3cf85cdde0
@ -99,6 +99,16 @@ const getSvgPath = (icon: string) => {
|
||||
vb: "0 0 512 512",
|
||||
path: "M575.8 255.5c0 18-15 32.1-32 32.1h-32l.7 160.2c0 2.7-.2 5.4-.5 8.1V472c0 22.1-17.9 40-40 40H456c-1.1 0-2.2 0-3.3-.1c-1.4 .1-2.8 .1-4.2 .1H416 392c-22.1 0-40-17.9-40-40V448 384c0-17.7-14.3-32-32-32H256c-17.7 0-32 14.3-32 32v64 24c0 22.1-17.9 40-40 40H160 128.1c-1.5 0-3-.1-4.5-.2c-1.2 .1-2.4 .2-3.6 .2H104c-22.1 0-40-17.9-40-40V360c0-.9 0-1.9 .1-2.8V287.6H32c-18 0-32-14-32-32.1c0-9 3-17 10-24L266.4 8c7-7 15-8 22-8s15 2 21 7L564.8 231.5c8 7 12 15 11 24z",
|
||||
};
|
||||
case 'articles':
|
||||
return {
|
||||
vb: "0 0 512 512",
|
||||
path: "M96 96c0-35.3 28.7-64 64-64H448c35.3 0 64 28.7 64 64V416c0 35.3-28.7 64-64 64H80c-44.2 0-80-35.8-80-80V128c0-17.7 14.3-32 32-32s32 14.3 32 32V400c0 8.8 7.2 16 16 16s16-7.2 16-16V96zm64 24v80c0 13.3 10.7 24 24 24H296c13.3 0 24-10.7 24-24V120c0-13.3-10.7-24-24-24H184c-13.3 0-24 10.7-24 24zm208-8c0 8.8 7.2 16 16 16h48c8.8 0 16-7.2 16-16s-7.2-16-16-16H384c-8.8 0-16 7.2-16 16zm0 96c0 8.8 7.2 16 16 16h48c8.8 0 16-7.2 16-16s-7.2-16-16-16H384c-8.8 0-16 7.2-16 16zM160 304c0 8.8 7.2 16 16 16H432c8.8 0 16-7.2 16-16s-7.2-16-16-16H176c-8.8 0-16 7.2-16 16zm0 96c0 8.8 7.2 16 16 16H432c8.8 0 16-7.2 16-16s-7.2-16-16-16H176c-8.8 0-16 7.2-16 16z",
|
||||
};
|
||||
case 'about':
|
||||
return {
|
||||
vb: "0 0 512 512",
|
||||
path: "M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z",
|
||||
};
|
||||
default:
|
||||
return { vb: "", path: "" }; // Default path or a placeholder icon
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ export default component$(() => {
|
||||
<li><a href="/"><Icon class="mr-2" icon="homepage" width={16} height={16} />Home</a></li>
|
||||
<li><a href="/"><Icon class="mr-2" icon="github" width={16} height={16} />GitHub</a></li>
|
||||
<li>
|
||||
<a href="/checklist"><Icon class="mr-2" icon="all" width={16} height={16} />Checklists</a>
|
||||
<a href="/checklist"><Icon class="mr-2" icon="all" width={16} height={16} />Checklists</a>
|
||||
<ul>
|
||||
{data.map((item: Section, index: number) => (
|
||||
<li key={`checklist-side-${index}`} class={`hover:bg-${item.color}-600 hover:bg-opacity-15`}>
|
||||
@ -102,14 +102,18 @@ export default component$(() => {
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/article">Articles</a>
|
||||
<a href="/article">
|
||||
<Icon class="mr-2" icon="articles" width={16} height={16} />Articles
|
||||
</a>
|
||||
<ul>
|
||||
<li><a href="/article/1">Article 1</a></li>
|
||||
<li><a href="/article/2">Article 2</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/about">About</a>
|
||||
<a href="/about">
|
||||
<Icon class="mr-2" icon="about" width={16} height={16} />About
|
||||
</a>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#">Contributing</a>
|
||||
|
81
web/src/components/psc/checklist-table.tsx
Normal file
81
web/src/components/psc/checklist-table.tsx
Normal file
@ -0,0 +1,81 @@
|
||||
import { component$ } from "@builder.io/qwik";
|
||||
|
||||
import type { Priority, Section } from '../../types/PSC';
|
||||
import { marked } from "marked";
|
||||
import { useLocalStorage } from "~/hooks/useLocalStorage";
|
||||
|
||||
export default component$((props: { section: Section }) => {
|
||||
|
||||
const STORAGE_KEY = 'PSC_PROGRESS';
|
||||
const [value, setValue] = useLocalStorage(STORAGE_KEY, {});
|
||||
|
||||
const getBadgeClass = (priority: Priority, precedeClass: string = '') => {
|
||||
switch (priority.toLocaleLowerCase()) {
|
||||
case 'recommended':
|
||||
return `${precedeClass}success`;
|
||||
case 'optional':
|
||||
return `${precedeClass}warning`;
|
||||
case 'advanced':
|
||||
return `${precedeClass}error`;
|
||||
default:
|
||||
return `${precedeClass}neutral`;
|
||||
}
|
||||
};
|
||||
|
||||
const generateId = (title: string) => {
|
||||
return title.toLowerCase().replace(/ /g, '-');
|
||||
};
|
||||
|
||||
const parseMarkdown = (text: string | undefined): string => {
|
||||
return marked.parse(text || '', { async: false }) as string || '';
|
||||
};
|
||||
|
||||
const isChecked = (point: string) => {
|
||||
const pointId = generateId(point);
|
||||
return value.value[pointId] || false;
|
||||
};
|
||||
|
||||
return (
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Done?</th>
|
||||
<th>Advice</th>
|
||||
<th>Level</th>
|
||||
<th>Details</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{props.section?.checklist.map((item, index) => (
|
||||
<tr key={index} class={`rounded-sm hover:bg-opacity-5 hover:bg-${getBadgeClass(item.priority)}`}>
|
||||
<td>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
id={generateId(item.point)}
|
||||
checked={isChecked(item.point)}
|
||||
onClick$={() => {
|
||||
const id = item.point.toLowerCase().replace(/ /g, '-');
|
||||
const data = value.value;
|
||||
data[id] = !data[id];
|
||||
setValue(data);
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<label class="text-base font-bold" for={generateId(item.point)}>
|
||||
{item.point}
|
||||
</label>
|
||||
</td>
|
||||
<td>
|
||||
<div class={`badge gap-2 ${getBadgeClass(item.priority, 'badge-')}`}>
|
||||
{item.priority}
|
||||
</div>
|
||||
</td>
|
||||
<td dangerouslySetInnerHTML={parseMarkdown(item.details)}></td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
});
|
50
web/src/components/psc/progress.tsx
Normal file
50
web/src/components/psc/progress.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
import { $, component$, useTask$, useSignal, useOnWindow, useContext } from "@builder.io/qwik";
|
||||
|
||||
import type { Priority, Sections, Section } from '../../types/PSC';
|
||||
import { useLocalStorage } from "~/hooks/useLocalStorage";
|
||||
import { ChecklistContext } from "~/store/checklist-context";
|
||||
|
||||
export default component$(() => {
|
||||
|
||||
const checklists = useContext(ChecklistContext);
|
||||
|
||||
const totalProgress = useSignal(0);
|
||||
|
||||
const STORAGE_KEY = 'PSC_PROGRESS';
|
||||
const [checkedItems] = useLocalStorage(STORAGE_KEY, {});
|
||||
|
||||
/**
|
||||
* Given an array of sections, returns the percentage completion of all included checklists.
|
||||
*/
|
||||
const calculateProgress = $((sections: Sections): number => {
|
||||
if (!checkedItems.value || !sections.length) {
|
||||
return 0;
|
||||
}
|
||||
const totalItems = sections.reduce((total: number, section: Section) => total + section.checklist.length, 0);
|
||||
let totalComplete = 0;
|
||||
sections.forEach((section: Section) => {
|
||||
section.checklist.forEach((item) => {
|
||||
const id = item.point.toLowerCase().replace(/ /g, '-');
|
||||
const isComplete = checkedItems.value[id];
|
||||
if (isComplete) {
|
||||
totalComplete++;
|
||||
}
|
||||
});
|
||||
});
|
||||
return Math.round((totalComplete / totalItems) * 100);
|
||||
});
|
||||
|
||||
useOnWindow('load', $(() => {
|
||||
calculateProgress(checklists.value)
|
||||
.then(percentage => {
|
||||
totalProgress.value = percentage;
|
||||
});
|
||||
}));
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>{totalProgress}</p>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -4,8 +4,8 @@ import { marked } from 'marked';
|
||||
|
||||
import Icon from '~/components/core/icon';
|
||||
import { ChecklistContext } from '~/store/checklist-context';
|
||||
import { useLocalStorage } from '~/hooks/useLocalStorage';
|
||||
import type { Section, Priority } from "~/types/PSC";
|
||||
import type { Section } from "~/types/PSC";
|
||||
import Table from '~/components/psc/checklist-table';
|
||||
|
||||
export default component$(() => {
|
||||
|
||||
@ -16,34 +16,9 @@ export default component$(() => {
|
||||
|
||||
const section: Section | undefined = checklists.value.find((item: Section) => item.slug === slug);
|
||||
|
||||
const getBadgeClass = (priority: Priority, precedeClass: string = '') => {
|
||||
switch (priority.toLocaleLowerCase()) {
|
||||
case 'recommended':
|
||||
return `${precedeClass}success`;
|
||||
case 'optional':
|
||||
return `${precedeClass}warning`;
|
||||
case 'advanced':
|
||||
return `${precedeClass}error`;
|
||||
default:
|
||||
return `${precedeClass}neutral`;
|
||||
}
|
||||
};
|
||||
|
||||
const generateId = (title: string) => {
|
||||
return title.toLowerCase().replace(/ /g, '-');
|
||||
};
|
||||
|
||||
const parseMarkdown = (text: string | undefined): string => {
|
||||
return marked.parse(text || '', { async: false }) as string || '';
|
||||
};
|
||||
|
||||
const STORAGE_KEY = 'PSC_PROGRESS';
|
||||
const [value, setValue] = useLocalStorage(STORAGE_KEY, {});
|
||||
|
||||
const isChecked = (point: string) => {
|
||||
const pointId = generateId(point);
|
||||
return value.value[pointId] || false;
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="md:my-8 md:px-16 sm:px-2 rounded-md">
|
||||
@ -55,48 +30,9 @@ export default component$(() => {
|
||||
<p class="py-2" dangerouslySetInnerHTML={parseMarkdown(section?.intro)}></p>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Done?</th>
|
||||
<th>Advice</th>
|
||||
<th>Level</th>
|
||||
<th>Details</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{section?.checklist.map((item, index) => (
|
||||
<tr key={index} class={`rounded-sm hover:bg-opacity-5 hover:bg-${getBadgeClass(item.priority)}`}>
|
||||
<td>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
id={generateId(item.point)}
|
||||
checked={isChecked(item.point)}
|
||||
onClick$={() => {
|
||||
const id = item.point.toLowerCase().replace(/ /g, '-');
|
||||
const data = value.value;
|
||||
data[id] = !data[id];
|
||||
setValue(data);
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<label class="text-base font-bold" for={generateId(item.point)}>
|
||||
{item.point}
|
||||
</label>
|
||||
</td>
|
||||
<td>
|
||||
<div class={`badge gap-2 ${getBadgeClass(item.priority, 'badge-')}`}>
|
||||
{item.priority}
|
||||
</div>
|
||||
</td>
|
||||
<td dangerouslySetInnerHTML={parseMarkdown(item.details)}></td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
{section && (<Table section={section} />)}
|
||||
</div>
|
||||
|
||||
</article>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { component$, useContext } from '@builder.io/qwik';
|
||||
import { type DocumentHead } from "@builder.io/qwik-city";
|
||||
|
||||
import Hero from "../components/furniture/hero";
|
||||
import SectionLinkGrid from "../components/psc/section-link-grid";
|
||||
import Hero from "~/components/furniture/hero";
|
||||
import SectionLinkGrid from "~/components/psc/section-link-grid";
|
||||
import Progress from "~/components/psc/progress";
|
||||
|
||||
import { ChecklistContext } from '~/store/checklist-context';
|
||||
|
||||
@ -12,6 +13,7 @@ export default component$(() => {
|
||||
return (
|
||||
<>
|
||||
<Hero />
|
||||
<Progress />
|
||||
<SectionLinkGrid sections={checklists.value} />
|
||||
</>
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user