mirror of
https://github.com/Lissy93/personal-security-checklist.git
synced 2024-10-01 01:35:37 -04:00
Adds progress saving, theme switching support and more
This commit is contained in:
parent
9610d59a3c
commit
7a1801cc85
@ -1,5 +1,4 @@
|
||||
{
|
||||
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "vite --mode ssr",
|
||||
@ -25,6 +24,7 @@
|
||||
"daisyui": "^3.0.2",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-plugin-qwik": "^1.4.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"postcss": "^8.4.21",
|
||||
"tailwindcss": "^3.2.4",
|
||||
"typescript": "^5.3.3",
|
||||
|
1
web/public/personal-security-checklist.yml
Symbolic link
1
web/public/personal-security-checklist.yml
Symbolic link
@ -0,0 +1 @@
|
||||
../../personal-security-checklist.yml
|
@ -94,6 +94,11 @@ const getSvgPath = (icon: string) => {
|
||||
vb: "0 0 512 512",
|
||||
path: "M0 72C0 49.9 17.9 32 40 32H88c22.1 0 40 17.9 40 40v48c0 22.1-17.9 40-40 40H40c-22.1 0-40-17.9-40-40V72zM0 232c0-22.1 17.9-40 40-40H88c22.1 0 40 17.9 40 40v48c0 22.1-17.9 40-40 40H40c-22.1 0-40-17.9-40-40V232zM128 392v48c0 22.1-17.9 40-40 40H40c-22.1 0-40-17.9-40-40V392c0-22.1 17.9-40 40-40H88c22.1 0 40 17.9 40 40zM160 72c0-22.1 17.9-40 40-40h48c22.1 0 40 17.9 40 40v48c0 22.1-17.9 40-40 40H200c-22.1 0-40-17.9-40-40V72zM288 232v48c0 22.1-17.9 40-40 40H200c-22.1 0-40-17.9-40-40V232c0-22.1 17.9-40 40-40h48c22.1 0 40 17.9 40 40zM160 392c0-22.1 17.9-40 40-40h48c22.1 0 40 17.9 40 40v48c0 22.1-17.9 40-40 40H200c-22.1 0-40-17.9-40-40V392zM448 72v48c0 22.1-17.9 40-40 40H360c-22.1 0-40-17.9-40-40V72c0-22.1 17.9-40 40-40h48c22.1 0 40 17.9 40 40zM320 232c0-22.1 17.9-40 40-40h48c22.1 0 40 17.9 40 40v48c0 22.1-17.9 40-40 40H360c-22.1 0-40-17.9-40-40V232zM448 392v48c0 22.1-17.9 40-40 40H360c-22.1 0-40-17.9-40-40V392c0-22.1 17.9-40 40-40h48c22.1 0 40 17.9 40 40z",
|
||||
};
|
||||
case 'homepage':
|
||||
return {
|
||||
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",
|
||||
};
|
||||
default:
|
||||
return { vb: "", path: "" }; // Default path or a placeholder icon
|
||||
}
|
||||
|
@ -1,13 +1,15 @@
|
||||
|
||||
import { component$ } from "@builder.io/qwik";
|
||||
import Icon from "~/components/core/icon";
|
||||
import { data } from '~/mock-data';
|
||||
import type { Section } from '~/types/PSC';
|
||||
import { useTheme } from '~/store/theme-store';
|
||||
|
||||
import Icon from "../core/icon";
|
||||
|
||||
import { data } from '../../mock-data';
|
||||
|
||||
import type { Section } from '../../types/PSC';
|
||||
|
||||
export default component$(() => {
|
||||
|
||||
const { theme, setTheme } = useTheme();
|
||||
|
||||
return (
|
||||
<>
|
||||
<input id="my-drawer-3" type="checkbox" class="drawer-toggle" />
|
||||
@ -62,7 +64,14 @@ export default component$(() => {
|
||||
</ul>
|
||||
<div class="tooltip tooltip-bottom" data-tip="Theme">
|
||||
<label class="cursor-pointer grid place-items-center">
|
||||
<input type="checkbox" value="synthwave" class="toggle theme-controller bg-base-content row-start-1 col-start-1 col-span-2"/>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={theme.theme === 'dark'}
|
||||
onClick$={() => {
|
||||
setTheme(theme.theme === 'dark' ? 'light' : 'dark');
|
||||
}}
|
||||
class="toggle theme-controller bg-base-content row-start-1 col-start-1 col-span-2"
|
||||
/>
|
||||
<svg class="col-start-1 row-start-1 stroke-base-100 fill-base-100" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.2 4.2l1.4 1.4M18.4 18.4l1.4 1.4M1 12h2M21 12h2M4.2 19.8l1.4-1.4M18.4 5.6l1.4-1.4"/></svg>
|
||||
<svg class="col-start-2 row-start-1 stroke-base-100 fill-base-100" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>
|
||||
</label>
|
||||
@ -77,10 +86,10 @@ export default component$(() => {
|
||||
<Icon class="mr-2" icon="shield" width={16} height={16} />
|
||||
Personal Security Checklist
|
||||
</h2>
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="/">GitHub</a></li>
|
||||
<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">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`}>
|
||||
@ -109,10 +118,10 @@ export default component$(() => {
|
||||
<a href="#">License</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#">Author</a>
|
||||
<ul>
|
||||
<li><a href="#">About</a></li>
|
||||
<li><a href="#">Contact</a></li>
|
||||
<li>
|
||||
<a href="#">Socials</a>
|
||||
@ -137,6 +146,7 @@ export default component$(() => {
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
31
web/src/hooks/useLocalStorage.ts
Normal file
31
web/src/hooks/useLocalStorage.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { $, type QRL, useOnWindow, useStore } from "@builder.io/qwik";
|
||||
|
||||
export function useLocalStorage(key: string, initialState: any): [any, QRL<(value: any) => void>] {
|
||||
const store = useStore({ value: initialState });
|
||||
|
||||
useOnWindow('load', $(() => {
|
||||
try {
|
||||
const item = window.localStorage.getItem(key);
|
||||
if (!item) {
|
||||
window.localStorage.setItem(key, JSON.stringify(initialState));
|
||||
}
|
||||
store.value = item ? JSON.parse(item) : initialState;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
store.value = initialState;
|
||||
}
|
||||
}));
|
||||
|
||||
const setValue$ = $((value: any) => {
|
||||
try {
|
||||
store.value = value;
|
||||
if (typeof window !== "undefined") {
|
||||
window.localStorage.setItem(key, JSON.stringify(value));
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
});
|
||||
|
||||
return [store, setValue$];
|
||||
}
|
@ -4,35 +4,43 @@ import type { DocumentHead } from "@builder.io/qwik-city";
|
||||
|
||||
export default component$(() => {
|
||||
return (
|
||||
<article class="bg-base-200 bg-opacity-25 p-8 mx-auto max-w-[1200px] m-8 rounded-lg shadow-lg">
|
||||
<h3>About the Author</h3>
|
||||
<p>
|
||||
This project was originally started by me, Alicia Sykes- with a lot of help from the community.
|
||||
</p>
|
||||
<br />
|
||||
<p>
|
||||
I write apps which aim to help people escape big tech, secure their data,
|
||||
and protect their privacy. I have a particular interest in self-hosting,
|
||||
Linux and OSINT.
|
||||
</p>
|
||||
<br />
|
||||
<p>
|
||||
If this type of stuff interests you, I maintain several other repositories that you may also like:
|
||||
</p>
|
||||
<br />
|
||||
<ul>
|
||||
<li>Web-Check - OSINT tool for analysing any website</li>
|
||||
<li>Dashy - Dashboard app, for organising your self-hosted services</li>
|
||||
<li>Portainer-Templates - Compiled repository of 1-click Docker apps for self-hosting</li>
|
||||
<li>AdGuardian - CLI tool for monitoring your networks traffic and AdGuard DNS stats</li>
|
||||
<li>Bug-Bounties - Database of websites which accept responsible vulnerability discolsure</li>
|
||||
<li>Awesome Privacy - A list of privacy-respscting software and services</li>
|
||||
<li>Email Comparison - Objective comparison of private/secure mail providers</li>
|
||||
<li>Git-In - Tools and resources to help beginners get into open source</li>
|
||||
</ul>
|
||||
<p>For a full list of projects I've published, see <a href="https://apps.aliciasykes.com/">apps.aliciasykes.com</a>, or follow me on GitHub (I'm <a href="https://github.com/lissy93">Lissy93</a>).</p>
|
||||
<div class="m-4 md:mx-16">
|
||||
<article class="bg-base-200 bg-opacity-25 p-8 mx-auto max-w-[1200px] m-8 rounded-lg shadow-lg">
|
||||
<h2 class="text-2xl">About the Security Checklist</h2>
|
||||
</article>
|
||||
<div class="divider"></div>
|
||||
<article class="bg-base-200 bg-opacity-25 p-8 mx-auto max-w-[1200px] m-8 rounded-lg shadow-lg">
|
||||
<h2 class="text-2xl">About the Author</h2>
|
||||
<p>
|
||||
This project was originally started by me, Alicia Sykes- with a lot of help from the community.
|
||||
</p>
|
||||
<br />
|
||||
<img class="ml-6 rounded-lg float-right" width="200" height="141" alt="Alicia Sykes Thames" src="https://i.ibb.co/s5SM1K7/DSC-0031-4.jpg" />
|
||||
<p>
|
||||
I write apps which aim to help people escape big tech, secure their data,
|
||||
and protect their privacy.
|
||||
</p>
|
||||
<br />
|
||||
<p>
|
||||
I have a particular interest in self-hosting, Linux and OSINT.
|
||||
So if this type of stuff interests you, check out these other projects that I maintain:
|
||||
</p>
|
||||
<br />
|
||||
<ul class="list-disc pl-8">
|
||||
<li>Web-Check - OSINT tool for analysing any website</li>
|
||||
<li>Dashy - Dashboard app, for organising your self-hosted services</li>
|
||||
<li>Portainer-Templates - Compiled repository of 1-click Docker apps for self-hosting</li>
|
||||
<li>AdGuardian - CLI tool for monitoring your networks traffic and AdGuard DNS stats</li>
|
||||
<li>Bug-Bounties - Database of websites which accept responsible vulnerability discolsure</li>
|
||||
<li>Awesome Privacy - A list of privacy-respscting software and services</li>
|
||||
<li>Email Comparison - Objective comparison of private/secure mail providers</li>
|
||||
<li>Git-In - Tools and resources to help beginners get into open source</li>
|
||||
</ul>
|
||||
<br />
|
||||
<p>For a full list of projects I've published, see <a href="https://apps.aliciasykes.com/">apps.aliciasykes.com</a>, or follow me on GitHub (I'm <a href="https://github.com/lissy93">Lissy93</a>).</p>
|
||||
|
||||
</article>
|
||||
</article>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -1,23 +1,23 @@
|
||||
import { component$ } from '@builder.io/qwik';
|
||||
import { component$, useContext } from '@builder.io/qwik';
|
||||
import { useLocation } from '@builder.io/qwik-city';
|
||||
import { marked } from 'marked';
|
||||
|
||||
import Icon from '../../../components/core/icon';
|
||||
import { data } from '../../../mock-data';
|
||||
import type { Section, Priority } from '../../../types/PSC';
|
||||
import Icon from '~/components/core/icon';
|
||||
import { ChecklistContext } from '~/store/checklist-context';
|
||||
import { useLocalStorage } from '~/hooks/useLocalStorage';
|
||||
import type { Section, Priority } from "~/types/PSC";
|
||||
|
||||
export default component$(() => {
|
||||
|
||||
const checklists = useContext(ChecklistContext);
|
||||
|
||||
const loc = useLocation();
|
||||
// const endpoint = useEndpoint<{ params: { title: string } }>();
|
||||
const slug = loc.params.title;
|
||||
|
||||
const section: Section | undefined = data.find((item: Section) => item.slug === slug);
|
||||
|
||||
// You can now use `title` to fetch data related to this checklist item
|
||||
// and render it below.
|
||||
const section: Section | undefined = checklists.value.find((item: Section) => item.slug === slug);
|
||||
|
||||
const getBadgeClass = (priority: Priority, precedeClass: string = '') => {
|
||||
switch (priority) {
|
||||
switch (priority.toLocaleLowerCase()) {
|
||||
case 'recommended':
|
||||
return `${precedeClass}success`;
|
||||
case 'optional':
|
||||
@ -37,7 +37,16 @@ export default component$(() => {
|
||||
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">
|
||||
<article class="bg-base-200 bg-opacity-25 p-8 mx-auto w-full max-w-[1200px] rounded-lg shadow-lg">
|
||||
<h1 class={['gap-2 text-5xl font-bold capitalize flex']}>
|
||||
<Icon height={36} width={36} icon={section?.icon || 'star'} />
|
||||
@ -59,7 +68,18 @@ export default component$(() => {
|
||||
{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)} />
|
||||
<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)}>
|
||||
@ -78,6 +98,7 @@ export default component$(() => {
|
||||
</table>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -1,13 +1,16 @@
|
||||
import { component$ } from "@builder.io/qwik";
|
||||
import { component$, useContext } from "@builder.io/qwik";
|
||||
|
||||
import { data } from '../../mock-data';
|
||||
import { ChecklistContext } from '~/store/checklist-context';
|
||||
import type { Section } from "~/types/PSC";
|
||||
|
||||
export default component$(() => {
|
||||
const checklists = useContext(ChecklistContext);
|
||||
|
||||
return (
|
||||
<main class="p-8">
|
||||
<div class="join join-vertical w-full">
|
||||
{data.map((section, index) => (
|
||||
<div key={index} class="collapse collapse-plus bg-base-200 join-item">
|
||||
{checklists.value.map((section: Section, index: number) => (
|
||||
<div key={index} class={['collapse collapse-plus bg-base-200 my-4', `border-double border-2 border-${section.color}-400`]}>
|
||||
<input type="radio" name="my-accordion-3" />
|
||||
<div class={['collapse-title text-xl font-medium', `bg-${section.color}-400`]}>
|
||||
<h3 class="text-slate-700">{section.title}</h3>
|
||||
|
@ -1,16 +1,18 @@
|
||||
import { component$ } from "@builder.io/qwik";
|
||||
import type { DocumentHead } from "@builder.io/qwik-city";
|
||||
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 { data } from '../mock-data';
|
||||
import { ChecklistContext } from '~/store/checklist-context';
|
||||
|
||||
export default component$(() => {
|
||||
export default component$(() => {
|
||||
const checklists = useContext(ChecklistContext);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Hero />
|
||||
<SectionLinkGrid sections={data} />
|
||||
<SectionLinkGrid sections={checklists.value} />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
@ -1,9 +1,23 @@
|
||||
import { component$, Slot } from "@builder.io/qwik";
|
||||
import type { RequestHandler } from "@builder.io/qwik-city";
|
||||
import { component$, useContextProvider, Slot } from "@builder.io/qwik";
|
||||
import { routeLoader$, type RequestHandler } from "@builder.io/qwik-city";
|
||||
|
||||
import Navbar from "../components/furniture/nav";
|
||||
import Footer from "../components/furniture/footer";
|
||||
|
||||
import { ChecklistContext } from "../store/checklist-context";
|
||||
|
||||
import { type Sections } from "~/types/PSC";
|
||||
import jsyaml from "js-yaml";
|
||||
|
||||
|
||||
export const useChecklists = routeLoader$(async () => {
|
||||
const url = import.meta.env.DEV ? `http://localhost:5173/personal-security-checklist.yml` : '/personal-security-checklist.yml';
|
||||
return await fetch(url)
|
||||
.then((res) => res.text())
|
||||
.then((res) => jsyaml.load(res) as Sections)
|
||||
.catch(() => []);
|
||||
});
|
||||
|
||||
export const onGet: RequestHandler = async ({ cacheControl }) => {
|
||||
cacheControl({
|
||||
staleWhileRevalidate: 60 * 60 * 24 * 7,
|
||||
@ -12,6 +26,10 @@ export const onGet: RequestHandler = async ({ cacheControl }) => {
|
||||
};
|
||||
|
||||
export default component$(() => {
|
||||
|
||||
const checklists = useChecklists();
|
||||
useContextProvider(ChecklistContext, checklists)
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* <Header /> */}
|
||||
|
8
web/src/store/checklist-context.ts
Normal file
8
web/src/store/checklist-context.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { type Signal } from '@builder.io/qwik';
|
||||
import { createContextId } from '@builder.io/qwik';
|
||||
|
||||
import type { Sections } from '../types/PSC';
|
||||
|
||||
export const ChecklistContext = createContextId<Signal<Sections>>(
|
||||
'psc.ChecklistContext'
|
||||
);
|
27
web/src/store/theme-store.ts
Normal file
27
web/src/store/theme-store.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { useStore, useOnWindow, $ } from '@builder.io/qwik';
|
||||
|
||||
import { useLocalStorage } from '~/hooks/useLocalStorage';
|
||||
|
||||
const STORAGE_KEY = 'PSC_THEME';
|
||||
const defaultTheme = 'dark';
|
||||
|
||||
export const useTheme = () => {
|
||||
const [theme, saveTheme] = useLocalStorage(STORAGE_KEY, defaultTheme);
|
||||
const state = useStore({ theme: theme.value });
|
||||
|
||||
useOnWindow('load', $(() => {
|
||||
const storedTheme = theme.value || defaultTheme;
|
||||
state.theme = storedTheme;
|
||||
document.getElementsByTagName('body')[0].setAttribute('data-theme', storedTheme);
|
||||
}));
|
||||
|
||||
const setTheme = $((newTheme: string) => {
|
||||
console.log('Updating Theme', newTheme);
|
||||
saveTheme(newTheme);
|
||||
state.theme = newTheme;
|
||||
document.getElementsByTagName('body')[0].setAttribute('data-theme', newTheme);
|
||||
});
|
||||
|
||||
return { theme: state, setTheme };
|
||||
};
|
||||
|
@ -3,6 +3,8 @@ export interface PersonalSecurityChecklist {
|
||||
sections: Section[],
|
||||
}
|
||||
|
||||
export type Sections = Section[];
|
||||
|
||||
export interface Section {
|
||||
title: string,
|
||||
slug: string,
|
||||
|
@ -40,7 +40,7 @@ module.exports = {
|
||||
},
|
||||
safelist: [
|
||||
{
|
||||
pattern: /(bg|outline|text|tw-color)-(yellow|lime|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|red)-(200|300|400|500|600)/,
|
||||
pattern: /(bg|outline|text|tw-color|border)-(yellow|lime|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|red)-(200|300|400|500|600)/,
|
||||
variants: ['light', 'dark', 'hover', 'focus'],
|
||||
},
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user