You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
rappli/src/components/WelcomeModal.tsx

766 lines
29 KiB
TypeScript

import {
Component,
createEffect,
createMemo,
onCleanup,
onMount,
useContext,
} from "solid-js";
import LufraiLogo from "~icons/custom/lufrai-logo";
import AppIcon from "~icons/custom/icon";
import ExternalLinkIcon from "~icons/carbon/launch";
import WarningIcon from "~icons/carbon/warning-alt-filled";
import LaunchIcon from "~icons/carbon/edit";
import DonationIcon from "~icons/carbon/favorite-filled";
import WatermarkIcon from "~icons/carbon/bullhorn";
import FreedomIcon from "~icons/noto/butterfly";
import FreeIcon from "~icons/noto/seedling";
import PrivacyIcon from "~icons/noto/princess";
import AgileIcon from "~icons/noto/person-bouncing-ball";
import ResultIcon from "~icons/noto/chequered-flag";
import HugIcon from "~icons/noto/hugging-face";
import Modal, { ModalCloseButton } from "./Modal";
import { LocalStoreContext } from "~/stores";
import createAccordion from "./Accordion";
import typer from "typer-js";
import "typer-js/dist/typer.min.css";
import {
externalLink,
getDisplayDate,
getDomain,
getHost,
onClickFocus,
shuffle,
} from "~/util";
import { capitalize } from "froebel";
export const description =
"Räppli ist eine freie Web App zur Erstellung von Schweizerischen Rechnungen inklusive QR-Code. Erfasse deine Rechnungspositionen und erhalte unmittelbar eine druckbare Rechnung.";
const WelcomeModal: Component = (props) => {
const [localState, setLocalState, localStateMounted] =
useContext(LocalStoreContext)!;
const [AcordionItem] = createAccordion();
let subtitleEl: HTMLSpanElement = undefined!;
const isOpen = createMemo(() => {
return localStateMounted() && localState.showWelcome;
});
onMount(function () {
let adjectives = [
"Schweizerischen",
"anständigen",
"umfassenden",
"erfrischenden",
"erfreulichen",
"sauberen",
"übersichtlichen",
];
let nouns = ["Rechnung", "Auftragsbestätigung", "Offerte"];
let combinations: [string, string][] = [];
for (const adjective of adjectives) {
for (const noun of nouns) {
combinations.push([adjective, noun]);
}
}
const firstCombination = combinations.splice(0, 1)[0];
combinations = shuffle(combinations);
combinations.unshift(firstCombination!);
let typerInstance = typer(subtitleEl, { min: 60, max: 160 });
let lastAdjective = "";
let lastNoun = "";
for (const [adjective, noun] of combinations) {
const first = lastAdjective === "";
if (lastAdjective === adjective) {
typerInstance.back(lastNoun.length, 80).continue(noun);
} else {
const method = first ? "line" : "continue";
typerInstance
.back("all", 65)
[method](
`${adjective} ${noun}`,
first ? { min: 60, max: 70 } : undefined
);
}
typerInstance.pause(first ? 4181 : 6765);
lastNoun = noun;
lastAdjective = adjective;
}
typerInstance.back("all", 50).repeat(Infinity);
let halted = false;
createEffect(function () {
if (!isOpen()) {
halted = typerInstance.halt() === undefined;
} else {
try {
// typer-js has some weird logic how it handles halt / resume. Essentially if halt wasn't executed before of resume, resume breaks completely
halted && typerInstance.resume();
} catch (err) {
if ((err as TypeError).message != "u.resume is not a function") {
console.dir(err);
}
}
halted = false;
}
});
onCleanup(function () {
typerInstance.kill();
});
});
const ShareonLink: Component<{ provider: string; via?: string }> = (
props
) => (
<a
class={props.provider + " shadow"}
title={capitalize(props.provider)}
data-via={props.via}
aria-label={`Share on ${capitalize(props.provider)}`}
></a>
);
// I hope Randomness will guarantee a fair sequence <3
const thankYouRahelAndFredi = shuffle([
<>
Fredi Niklaus (
<a
class="inline-flex items-center gap-1"
href="https://www.remedyit.ch/"
{...externalLink}
>
RemedyIT <ExternalLinkIcon />
</a>
)
</>,
"Rahel Lutz",
]);
thankYouRahelAndFredi.splice(1, 0, " und ");
return (
<Modal open={isOpen()}>
<div class="hidden xl:block">
<ModalCloseButton onClick={() => setLocalState("showWelcome", false)} />
</div>
<div class="max-h-[60vh] overflow-y-auto px-2">
<div class="min-h-[60vh] flex items-center justify-center">
<div>
<div class="flex flex-col items-center justify-around">
<div class="mt-3 mb-3 text-6xl lg:text-8xl flex items-end lg:w-[370px] justify-between gap-2 lg:gap-0 font-bold tracking-tighter leading-none text-swiss-red fill-current">
<AppIcon class="w-auto h-[0.9em]" />
<div>Räppli</div>
</div>
<div class="flex flex-col items-stretch">
<div class="flex justify-center">
<div class="text-gray-800 text-sm lg:text-base tracking-widest mb-4 flex gap-1 flex-col items-center lg:flex-row">
Der reibungslose Weg zur{" "}
<span class="ignore-white-space" ref={subtitleEl}></span>
</div>
</div>
</div>
<div class="flex justify-end lg:w-[410px] max-w-full">
<a
href="https://lufrai.org"
class="text-lufrai-primary-darker hover:text-lufrai-primary transition-colors fill-current text-base flex items-center leading-tight gap-2 tracking-tighter font-bold border-b border-lufrai-primary-light border-opacity-20 pb-1"
{...externalLink}
>
<span>made</span>
<span>by</span>
<LufraiLogo class="w-auto h-[1.5em]" />
</a>
</div>
</div>
<div class="mt-28 text-base flex flex-wrap text-center gap-7 items-center justify-center">
<a
class="link text-secondary-focus hover:text-secondary"
href="#welcome-quickstart"
>
Einleitung
</a>
<a
class="link text-secondary-focus hover:text-secondary"
href="#welcome-why-free"
>
Wieso ist Räppli <br />
<strong>komplett kostenlos</strong>?
</a>
<a
class="link text-secondary-focus hover:text-secondary"
href="#welcome-lufrai"
>
Was ist Lufrai?
</a>
<a
class="link text-secondary-focus hover:text-secondary"
href="#welcome-patron"
>
Das Projekt
<br />
unterstützen
</a>
<a
class="link text-secondary-focus hover:text-secondary flex items-center gap-2"
href="#welcome-opensource"
>
Open Source
</a>
<a
class="link text-secondary-focus hover:text-secondary flex items-center gap-2"
href="#welcome-thankyou"
>
Danksagungen
</a>
<a
class="link text-secondary-focus hover:text-secondary"
title="Häufig gestellte Fragen"
href="#welcome-faq"
>
FAQ
</a>
</div>
<div class="mt-16 flex flex-wrap gap-3 items-center justify-center">
<div class="text-sm text-slate-600">Teile es auf:</div>
<div
class="shareon flex flex-wrap items-center justify-center"
data-title="Hast du schon von Räppli gehört? Es ist ein praktisches Web App, womit Schweizer QR Rechnungen inkl. Rechnungspositionen erstellt werden können und es ist komplett kostenlos! Probiere es aus unter:"
data-url={getHost()}
>
<ShareonLink provider="mastodon" />
<ShareonLink provider="telegram" />
<ShareonLink provider="reddit" />
<ShareonLink provider="odnoklassniki" />
<ShareonLink provider="pinterest" />
<ShareonLink provider="pocket" />
<ShareonLink provider="viber" />
<ShareonLink provider="vkontakte" />
<ShareonLink provider="linkedin" />
<ShareonLink provider="twitter" via="katy_wings" />
<ShareonLink provider="facebook" />
<ShareonLink provider="whatsapp" />
</div>
</div>
</div>
</div>
<div class="mt-20 prose text-opacity-100">
<section>
<h2 id="welcome-quickstart">Einleitung</h2>
<p class="text-xl font-light lead">{description}</p>
<p>
Du möchtest dich <strong>selbstständig machen</strong>, gründest
gerade ein <strong>Startup</strong> oder einen{" "}
<strong>Verein</strong> und musst in der Lage sein{" "}
<strong>Rechnungen, Offerten und Auftragsbestätigungen</strong> zu
verschicken? Du möchtest dabei <strong>unabhängig</strong>{" "}
bleiben? Dann bist du hier genau richtig!
</p>
<p>Vorteile von Räppli:</p>
<ul>
<li>
<ResultIcon class="inline-block scale-150 mr-2" /> Die
praktische Vorschau zeigt dir sofort das Endergebnis.
</li>
<li>
<PrivacyIcon class="inline-block scale-150 mr-2" />
Deine Daten bleiben{" "}
<a onClick={onClickFocus} href="#welcome-privacy">
bei dir
</a>
.
</li>
<li>
<FreeIcon class="inline-block scale-150 mr-2" /> Räppli ist und
bleibt <a href="#welcome-why-free">komplett kostenlos</a>!
</li>
<li>
<FreedomIcon class="inline-block scale-150 mr-2" /> Der
Quellcode ist{" "}
<a href="#welcome-opensource">vollumfänglich öffentlich</a>.
Räppli kann selbst gehostet werden!
</li>
<li>
<AgileIcon class="inline-block scale-150 mr-2" /> Du arbeitest{" "}
<a onClick={onClickFocus} href="#welcome-agile">
agil
</a>
? Räppli kann dir dabei helfen einen Preis zu kalkulieren!
</li>
</ul>
</section>
<section class="mt-16">
<h2 id="welcome-why-free">
Wieso ist <span class="text-swiss-red">Räppli</span> komplett{" "}
<span class="text-swiss-red">kostenlos</span>?
</h2>
<p>
Consectetur id magna labore commodo exercitation laboris est
laboris consectetur irure minim. Officia anim tempor adipisicing
irure labore tempor reprehenderit culpa consequat ea esse
exercitation. Consectetur labore velit nulla excepteur eiusmod sit
fugiat do proident. Ex non consectetur mollit dolor dolore Lorem
ut et incididunt pariatur sunt deserunt tempor magna ullamco.
</p>
</section>
<section class="mt-16">
<h2 id="welcome-lufrai">
Was ist <span class="text-lufrai-primary">Lufrai</span>?
</h2>
<p>
Lufrai ist die Einzelfirma von{" "}
<a
class="inline-flex items-center gap-2"
href="https://lufrai.org/katjalutz"
{...externalLink}
>
Katja Lutz <ExternalLinkIcon />
</a>
. Sie setzt sich als Web-Entwicklerin, Künstlerin und Aktivistin
für eine unabhängige, freie Schweiz ein und unterstützt ihre
souveränen Mitmenschen, das Steuer selbst in die Hand zu nehmen.
<a
class="btn btn-sm inline-flex ml-3 items-center gap-2"
href="https://lufrai.org"
{...externalLink}
>
Erfahre mehr <ExternalLinkIcon />
</a>
</p>
<p>Grundsätze:</p>
<ol class="prose-sm">
<li>Zuoberst steht der Mensch.</li>
<li>
Selbstverantwortlich zu leben heisst, zu geben und anzunehmen.
<br />
Miteinander zu leben heisst, zu vergeben.
</li>
<li>
Erkenntnis kommt von Innen und Altruismus kann nicht erzwungen
werden.
</li>
<li>
Nötigung drängt den Horizont zusammen, Aufklärung erweitert ihn.
</li>
<li>
Gleichheit setzt voraus, sich auf Augenhöhe zu begegnen und
Privatsphäre zu respektieren.
</li>
<li>
Achtsam ist, wer global denkt, aber lokal handelt. <br />
Gute Entscheidungen trifft, wer die Konsequenzen begreift.
</li>
<li>
Der Technik die Entscheidung zu überlassen heisst, selbst zur
Maschine zu werden.
</li>
<li>
Schönheit ist nicht selbstverständlich, sie entspringt aus der
Bereitschaft zur Veränderung.
</li>
<li>
Anführer zeichnen sich nicht durch ihre Lautstärke und Forderung
aus, sondern durch Selbstlosigkeit und Transparenz.
</li>
<li>Zensur betreibt, wer etwas verbergen will.</li>
</ol>
</section>
<section class="mt-16">
<h2 id="welcome-patron">Das Projekt unterstützen</h2>
<p>
Irure laboris quis consequat enim tempor dolor. Esse velit
occaecat dolore aute cillum pariatur reprehenderit irure duis eu
nulla pariatur fugiat consectetur ipsum. Commodo ad aliquip nulla
non incididunt. Officia veniam cillum cillum. Velit ex aliquip
mollit deserunt nostrud id amet voluptate ea duis cupidatat
officia culpa consequat enim. Voluptate anim fugiat anim et elit
aute cupidatat. Duis aliquip laboris adipisicing dolore elit
voluptate proident occaecat excepteur culpa exercitation velit.
Veniam esse quis voluptate aliquip culpa. Aute cupidatat sunt amet
ad fugiat id elit. Officia proident ea deserunt quis anim elit
cupidatat pariatur. Aliqua dolore ad eiusmod qui eu ea enim qui
enim incididunt aute irure tempor fugiat sunt. Consequat magna
culpa Lorem nostrud cillum eu cillum adipisicing eu aute duis
excepteur. In anim mollit amet elit ex.
</p>
<h3>Spende</h3>
<h3>Mitwirkung</h3>
</section>
<section class="mt-16">
<h2 id="welcome-opensource">Open Source</h2>
<p>
Du hast richtig gehört, Räppli ist Open Source! Jeder kann Räppli
kopieren, verteilen und erweitern. Du brauchst also keine Angst zu
haben, dass du irgendwann keine Rechnungen mehr schreiben darfst.
Lizenziert ist Räppli mit{" "}
<a
class="inline-flex items-center gap-1"
href="https://git.lufrai.com/rappli/rappli/raw/branch/master/LICENSE"
>
MIT
<ExternalLinkIcon />
</a>
.
<a
class="btn btn-sm inline-flex ml-3 items-center gap-2"
href="https://git.lufrai.com/rappli/rappli"
{...externalLink}
>
Git-Repository
<ExternalLinkIcon />
</a>
<br />
<strong>
Falls du dich dort zur Mitwirkung registrieren möchtest: Bitte
schreibe mir nach deiner Registrierung ein kurzes Mail mit
deiner Motivation an "contact(at)lufrai.org", damit ich dich
freischalten kann.
</strong>
</p>
<p>
Eingesetzte Technologien (<small>Nur um einige zu nennen</small>):
</p>
<ul>
<li>
<a
class="flex items-center gap-2"
href="https://www.solidjs.com/"
{...externalLink}
>
Solid
<ExternalLinkIcon />
</a>
</li>
<li>
<a
class="flex items-center gap-2"
href="https://tailwindcss.com/"
{...externalLink}
>
Tailwind CSS
<ExternalLinkIcon />
</a>
</li>
<li>
<a
class="flex items-center gap-2"
href="https://daisyui.com/"
{...externalLink}
>
daisyUI
<ExternalLinkIcon />
</a>
</li>
<li>
<a
class="flex items-center gap-2"
href="https://sortablejs.github.io/Sortable/"
{...externalLink}
>
Sortable
<ExternalLinkIcon />
</a>
</li>
<li>
<a
class="flex items-center gap-2"
href="https://remark.js.org/"
{...externalLink}
>
remark
<ExternalLinkIcon />
</a>
</li>
</ul>
</section>
<section class="mt-16">
<h2 id="welcome-thankyou">Danksagungen</h2>
<p>
An dieser Stelle möchte ich mich bei der Community von{" "}
<a
class="inline-flex items-center gap-1 mr-1"
href="https://www.solidjs.com/"
{...externalLink}
>
Solid <ExternalLinkIcon />
</a>
bedanken, nicht nur für ihr verblüffend effektives Open Source
Web-Framework, sondern auch für ihre Offenheit, Anregungen und
cleveren Tipps!
</p>
<p>
Ausserdem möchte ich (Katja) mich insbesondere bei{" "}
{thankYouRahelAndFredi} herzlichst bedanken, welche mir stets bei
der Umsetzung meiner kurligen Projekte unterstützend zur Seite
standen und mich immer inspirierten, einen Schritt weiterzugehen!
</p>
</section>
<section class="mt-16">
<h2 id="welcome-faq">Häufig gestellte Fragen</h2>
<div class="text-black">
<AcordionItem
id="welcome-privacy"
label={"1. Wo werden welche Daten gespeichert?"}
alignCenter={false}
>
<ul>
<li>
Deine eingegebenen Daten werden an keine Server übermittelt.
</li>
<li>
Einige deiner Daten, wie z.B. Bankverbindung, Logo und
Zahlungsbedingungen verbleiben im{" "}
<span
class="border-b border-black border-dotted"
title="Der sogennante localStorage"
>
Speicher
</span>{" "}
von deinem Browser. So musst du sie nicht jedes Mal neu
erfassen, wenn du eine Rechnung schreiben willst.
</li>
<li>
Damit das Internet und somit auch Räppli überhaupt
funktionieren, müssen jedoch unweigerlich einige Daten
übertragen werden, wie z.B. deine IP-Adresse. Erfahre{" "}
<a
class="inline-flex items-center gap-1"
href="https://t3n.de/news/tcp-ip-internet-grundlagen-755667/"
{...externalLink}
>
hier
<ExternalLinkIcon />
</a>{" "}
wie das Internet funktioniert.
</li>
</ul>
</AcordionItem>
<AcordionItem
label="2. Ich habe vergessen zu speichern, was nun?!"
alignCenter={false}
>
Deine Daten werden auf keinem Server gespeichert. Lerne aus dem
Malheur und speichere ab sofort regelmässig{" "}
<HugIcon class="inline" />!
</AcordionItem>
<AcordionItem
label={
<>
3. Ich habe einen Anpassungswunsch!
<br />
Kann die App bitte gratis angepasst werden?
</>
}
alignCenter={false}
>
test
</AcordionItem>
<AcordionItem
id="welcome-agile"
label={
<div>
4. Wie können Aufwand-Schätzungen gemacht werden?
<br />
Was hat es mit <strong>Agile</strong> auf sich?
</div>
}
alignCenter={false}
>
test
</AcordionItem>
<AcordionItem
label={"5. Welche Geräte werden unterstützt?"}
alignCenter={false}
>
<p class="!mt-0">
Räppli wurde für Geräte mit grösseren Bildschirmen wie z.B.
Laptops und Computer programmiert. Die besten Druckergebnisse
werden erfahrungsgemäss mit dem Browser "Mozilla Firefox"
erreicht, jedoch sind auch "Google Chrome" basierte Browser
geeignet. Verwende einen aktuellen Browser!
</p>
</AcordionItem>
<AcordionItem
label={
<div>
6. Können Fliesstexte formatiert werden?
<br />
Was ist <strong>Markdown</strong>?
</div>
}
alignCenter={false}
>
test
</AcordionItem>
<AcordionItem
id="welcome-local-installation"
label={<div>7. Kann Räppli lokal installiert werden?</div>}
alignCenter={false}
>
<p class="!mt-0">
Räppli kann lokal installiert werden, jedoch ist es für den
Betrieb auf Servern optimiert. Das heisst: auch wenn du Räppli
installiert hast, musst du deinen Webbrowser nutzen, um Räppli
zu öffnen.
</p>
<p class="bg-slate-500/10 flex p-3 items-center gap-3 rounded">
<WarningIcon class="w-auto h-6 inline mr-1" />{" "}
<strong>
Für die Installation sind grundlegende Kenntnisse im Umgang
mit der Kommandozeile empfohlen!
</strong>
</p>
<h3>Voraussetzungen:</h3>
<ul>
<li>
Installiere{" "}
<a
class="inline-flex items-center gap-1"
href="https://nodejs.org/en/"
{...externalLink}
>
nodejs {">"}= 18 <ExternalLinkIcon />
</a>
</li>
</ul>
<h3>Installation Räppli</h3>
<p>Führe folgende Befehle aus:</p>
<ul>
<li>
<pre>npm install -g rappli</pre>
</li>
</ul>
<h3>Räppli starten</h3>
<p>Führe folgenden Befehl aus, um Räppli zu starten</p>
<ul>
<li>
<pre>rappli</pre>
</li>
<li>
Sobald die Meldung "Listening on port 3000" erscheint,
kannst du dein lokales Räppli auf deinem Browser über{" "}
<a href="http://localhost:3000" {...externalLink}>
http://localhost:3000
</a>{" "}
erreichen.
</li>
</ul>
</AcordionItem>
<AcordionItem
label={
<div>
8. Wie präzise wurden die SIX QR-Rechnung Vorgaben
umgesetzt?
</div>
}
alignCenter={false}
>
<p class="!mt-0">
Es handelt sich bewusst nicht um eine Pixel-genaue Umsetzung
der Vorgaben von SIX. Das Design der QR-Rechnung orientiert
sich am Grundsatz des pragmatischen Perfektionismus. Das
heisst: es wurden die Möglichkeiten des Design Frameworks
"Tailwind CSS" genutzt, um die Vorgaben von SIX möglichst
genau umzusetzen, aber da dabei nicht die unerschöpflichen
Mittel der Schweizer Regierung zur Verfügung standen, wurde
das Rad nicht komplett neu erfunden.
</p>
</AcordionItem>
<AcordionItem
label={<div>9. Haftungsausschluss</div>}
alignCenter={false}
>
<p class="!mt-0">
Räppli ({getDomain()}) verwendest du{" "}
<strong>eigenverantwortlich</strong>. "Lufrai - Katja Lutz"
(Einzelfirma) und "Katja Lutz" (Mensch) übernehmen keine
Haftung.
</p>
<p>
Es besteht kein Anspruch auf die Verfügbarkeit von{" "}
{getDomain()}. Falls du 100% sicher sein willst, dass du
Räppli auch in aussergewöhnlichen Zeiten verwenden kannst,
besorge dir den Quellcode jetzt und{" "}
<a href="#welcome-local-installation" onClick={onClickFocus}>
installiere Räppli lokal
</a>
.
</p>
</AcordionItem>
</div>
</section>
</div>
</div>
<div class="mt-14 relative flex justify-center items-center gap-10">
<a
href="#welcome-patron"
class="hidden xl:flex btn btn-sm btn-secondary bg-purple-600 border-purple-600 gap-2 transition-all shadow-lg hover:shadow-lg shadow-purple-300 hover:shadow-purple-500 hover:ring-2 ring-offset-2 ring-purple-500"
>
<DonationIcon />
Spenden
</a>
<div
class="hidden xl:flex form-control flex-row items-center gap-2 tooltip tooltip-accent"
data-tip="Füge das Lufrai Logo zu deinem Dokument hinzu."
>
<input
id="welcome-modal-show-watermark"
checked={localState.showLufraiWatermark}
class="checkbox checkbox-lg checkbox-accent shadow-md"
type="checkbox"
onClick={(evt) =>
setLocalState("showLufraiWatermark", evt.currentTarget.checked)
}
/>
<label
for="welcome-modal-show-watermark"
class="btn btn-sm btn-accent gap-2 shadow-md"
>
<WatermarkIcon />
Für Lufrai werben
</label>
</div>
<button
onClick={() => setLocalState("showWelcome", false)}
class="hidden xl:flex btn btn-xl btn-primary gap-2 transition-all shadow-lg hover:shadow-lg shadow-indigo-300 hover:shadow-indigo-500 hover:ring-2 ring-offset-2 ring-indigo-500"
>
<LaunchIcon />
Loslegen
</button>
<div class="w-full text-sm xl:hidden bg-error bg-opacity-50 ring-2 ring-offset-2 ring-error text-error-content rounded p-3 shadow-2xl flex items-center justify-center gap-3">
<WarningIcon /> Bitte verwende einen Laptop oder Computer!
</div>
</div>
<div class="absolute bottom-8 right-8 text-xs text-black opacity-60 flex gap-3">
<span>Version: {__APP_VERSION__}</span>
<span>{getDisplayDate(new Date(__BUILD_TIME__))}</span>
</div>
</Modal>
);
};
export default WelcomeModal;