From 7a1801cc8503b7a45fccd8f9a18adac78d13d791 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Mon, 5 Feb 2024 20:38:14 +0000 Subject: [PATCH] Adds progress saving, theme switching support and more --- web/package.json | 2 +- web/public/personal-security-checklist.yml | 1 + web/src/components/core/icon.tsx | 5 ++ web/src/components/furniture/nav.tsx | 30 ++++++---- web/src/hooks/useLocalStorage.ts | 31 +++++++++++ web/src/routes/about/index.tsx | 64 ++++++++++++---------- web/src/routes/checklist/[title]/index.tsx | 43 +++++++++++---- web/src/routes/checklist/index.tsx | 11 ++-- web/src/routes/index.tsx | 12 ++-- web/src/routes/layout.tsx | 22 +++++++- web/src/store/checklist-context.ts | 8 +++ web/src/store/theme-store.ts | 27 +++++++++ web/src/types/PSC.ts | 2 + web/tailwind.config.js | 2 +- 14 files changed, 198 insertions(+), 62 deletions(-) create mode 120000 web/public/personal-security-checklist.yml create mode 100644 web/src/hooks/useLocalStorage.ts create mode 100644 web/src/store/checklist-context.ts create mode 100644 web/src/store/theme-store.ts diff --git a/web/package.json b/web/package.json index a001a0d..b39d011 100644 --- a/web/package.json +++ b/web/package.json @@ -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", diff --git a/web/public/personal-security-checklist.yml b/web/public/personal-security-checklist.yml new file mode 120000 index 0000000..c1946c9 --- /dev/null +++ b/web/public/personal-security-checklist.yml @@ -0,0 +1 @@ +../../personal-security-checklist.yml \ No newline at end of file diff --git a/web/src/components/core/icon.tsx b/web/src/components/core/icon.tsx index 054db3d..edee290 100644 --- a/web/src/components/core/icon.tsx +++ b/web/src/components/core/icon.tsx @@ -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 } diff --git a/web/src/components/furniture/nav.tsx b/web/src/components/furniture/nav.tsx index 7732867..2482cb7 100644 --- a/web/src/components/furniture/nav.tsx +++ b/web/src/components/furniture/nav.tsx @@ -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 ( <> @@ -62,7 +64,14 @@ export default component$(() => {
@@ -77,10 +86,10 @@ export default component$(() => { Personal Security Checklist -
  • Home
  • -
  • GitHub
  • +
  • Home
  • +
  • GitHub
  • - Checklists + Checklists
      {data.map((item: Section, index: number) => (
    • @@ -109,10 +118,10 @@ export default component$(() => { License
    +
  • diff --git a/web/src/hooks/useLocalStorage.ts b/web/src/hooks/useLocalStorage.ts new file mode 100644 index 0000000..427ee93 --- /dev/null +++ b/web/src/hooks/useLocalStorage.ts @@ -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$]; +} diff --git a/web/src/routes/about/index.tsx b/web/src/routes/about/index.tsx index f0aba14..e371f3d 100644 --- a/web/src/routes/about/index.tsx +++ b/web/src/routes/about/index.tsx @@ -4,35 +4,43 @@ import type { DocumentHead } from "@builder.io/qwik-city"; export default component$(() => { return ( -
    -

    About the Author

    -

    - This project was originally started by me, Alicia Sykes- with a lot of help from the community. -

    -
    -

    - 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. -

    -
    -

    - If this type of stuff interests you, I maintain several other repositories that you may also like: -

    -
    - -

    For a full list of projects I've published, see apps.aliciasykes.com, or follow me on GitHub (I'm Lissy93).

    +
    +
    +

    About the Security Checklist

    +
    +
    +
    +

    About the Author

    +

    + This project was originally started by me, Alicia Sykes- with a lot of help from the community. +

    +
    + Alicia Sykes Thames +

    + 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. + So if this type of stuff interests you, check out these other projects that I maintain: +

    +
    +
      +
    • Web-Check - OSINT tool for analysing any website
    • +
    • Dashy - Dashboard app, for organising your self-hosted services
    • +
    • Portainer-Templates - Compiled repository of 1-click Docker apps for self-hosting
    • +
    • AdGuardian - CLI tool for monitoring your networks traffic and AdGuard DNS stats
    • +
    • Bug-Bounties - Database of websites which accept responsible vulnerability discolsure
    • +
    • Awesome Privacy - A list of privacy-respscting software and services
    • +
    • Email Comparison - Objective comparison of private/secure mail providers
    • +
    • Git-In - Tools and resources to help beginners get into open source
    • +
    +
    +

    For a full list of projects I've published, see apps.aliciasykes.com, or follow me on GitHub (I'm Lissy93).

    -
    +
    + ); }); diff --git a/web/src/routes/checklist/[title]/index.tsx b/web/src/routes/checklist/[title]/index.tsx index fd6364c..3fa0934 100644 --- a/web/src/routes/checklist/[title]/index.tsx +++ b/web/src/routes/checklist/[title]/index.tsx @@ -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 ( +

    @@ -59,7 +68,18 @@ export default component$(() => { {section?.checklist.map((item, index) => ( - + { + const id = item.point.toLowerCase().replace(/ /g, '-'); + const data = value.value; + data[id] = !data[id]; + setValue(data); + }} + />

    + ); }); diff --git a/web/src/routes/checklist/index.tsx b/web/src/routes/checklist/index.tsx index 060f5d6..e7a7ccb 100644 --- a/web/src/routes/checklist/index.tsx +++ b/web/src/routes/checklist/index.tsx @@ -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 (
    - {data.map((section, index) => ( -
    + {checklists.value.map((section: Section, index: number) => ( +

    {section.title}

    diff --git a/web/src/routes/index.tsx b/web/src/routes/index.tsx index aa5f429..44f9965 100644 --- a/web/src/routes/index.tsx +++ b/web/src/routes/index.tsx @@ -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 ( <> - + ); }); diff --git a/web/src/routes/layout.tsx b/web/src/routes/layout.tsx index 5d7f1cf..dbd29bd 100644 --- a/web/src/routes/layout.tsx +++ b/web/src/routes/layout.tsx @@ -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 ( <> {/*
    */} diff --git a/web/src/store/checklist-context.ts b/web/src/store/checklist-context.ts new file mode 100644 index 0000000..2a0714c --- /dev/null +++ b/web/src/store/checklist-context.ts @@ -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>( + 'psc.ChecklistContext' +); diff --git a/web/src/store/theme-store.ts b/web/src/store/theme-store.ts new file mode 100644 index 0000000..a92c622 --- /dev/null +++ b/web/src/store/theme-store.ts @@ -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 }; +}; + diff --git a/web/src/types/PSC.ts b/web/src/types/PSC.ts index c4bc43d..30fe5cb 100644 --- a/web/src/types/PSC.ts +++ b/web/src/types/PSC.ts @@ -3,6 +3,8 @@ export interface PersonalSecurityChecklist { sections: Section[], } +export type Sections = Section[]; + export interface Section { title: string, slug: string, diff --git a/web/tailwind.config.js b/web/tailwind.config.js index d0f9e46..0e4445d 100644 --- a/web/tailwind.config.js +++ b/web/tailwind.config.js @@ -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'], }, {