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.
1095 lines
42 KiB
TypeScript
1095 lines
42 KiB
TypeScript
import {
|
|
Component,
|
|
createEffect,
|
|
createMemo,
|
|
FlowComponent,
|
|
onCleanup,
|
|
onMount,
|
|
JSX,
|
|
useContext,
|
|
splitProps,
|
|
createSignal,
|
|
Show,
|
|
For,
|
|
untrack,
|
|
createResource,
|
|
} from "solid-js";
|
|
import { createStore } from "solid-js/store";
|
|
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/bxs/donate-heart";
|
|
import FavoriteIcon from "~icons/carbon/favorite";
|
|
import FavoriteHalfIcon from "~icons/carbon/favorite-half";
|
|
import FavoriteFilledIcon 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 VideoIcon from "~icons/carbon/video-filled";
|
|
import CloseIcon from "~icons/carbon/close-outline";
|
|
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,
|
|
} from "~/util";
|
|
import { capitalize, clamp, shuffle } from "froebel";
|
|
import { markdownHelpUrl } from "./Markdown";
|
|
import AgileCalculator from "./AgileCalculator";
|
|
import server from "solid-start/server";
|
|
import { getLikes, increaseLikes } from "~/server/likes";
|
|
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 externalLinkClass = "inline-flex items-center gap-1";
|
|
import ConfettiExplosion from "solid-confetti-explosion";
|
|
// TODO: after solid-start update >= alpha.95, switch to lazy
|
|
// const ConfettiExplosion = lazy(() => import("solid-confetti-explosion"));
|
|
|
|
const ExternalLink: FlowComponent<
|
|
JSX.AnchorHTMLAttributes<HTMLAnchorElement>
|
|
> = (p) => {
|
|
const [props, rest] = splitProps(p, ["children"]);
|
|
return (
|
|
<a class={externalLinkClass} {...externalLink} {...rest}>
|
|
{props.children}
|
|
<ExternalLinkIcon />
|
|
</a>
|
|
);
|
|
};
|
|
|
|
const WelcomeModal: Component = (props) => {
|
|
const [localState, setLocalState, localStateMounted] =
|
|
useContext(LocalStoreContext)!;
|
|
const [AccordionItem] = createAccordion();
|
|
let subtitleEl: HTMLSpanElement = undefined!;
|
|
const isOpen = createMemo(() => {
|
|
return localStateMounted() && localState.showWelcome;
|
|
});
|
|
const [showTrailer, setShowTrailer] = createSignal(false);
|
|
const increaseCount_ = server(increaseLikes);
|
|
const [likes] = createResource(server(getLikes));
|
|
|
|
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.endsWith("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={externalLinkClass}
|
|
href="https://www.remedyit.ch/"
|
|
{...externalLink}
|
|
>
|
|
RemedyIT <ExternalLinkIcon />
|
|
</a>
|
|
)
|
|
</>,
|
|
"Rahel Lutz",
|
|
]);
|
|
thankYouRahelAndFredi.splice(1, 0, " und ");
|
|
const [likeCount, setLikeCount] = createSignal(0);
|
|
const [confetti, setConfetti] = createStore([false, false, false]);
|
|
|
|
const confettiDuration = 2000;
|
|
createEffect(function () {
|
|
if (likeCount() == 0) {
|
|
return;
|
|
}
|
|
untrack(function () {
|
|
for (const idx of confetti.keys()) {
|
|
if (confetti[idx]) {
|
|
continue;
|
|
}
|
|
|
|
setConfetti(idx, true);
|
|
setTimeout(
|
|
() => setConfetti(idx, false),
|
|
(confettiDuration / 100) * 80
|
|
);
|
|
break;
|
|
}
|
|
});
|
|
});
|
|
|
|
return (
|
|
<Modal open={isOpen()}>
|
|
<div class="hidden lg: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 font-[Cantarell,_system-ui,_sans-serif]">
|
|
<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>
|
|
|
|
<Show when={showTrailer()}>
|
|
{() => (
|
|
<div class="absolute top-0 left-0 h-full w-full flex flex-col justify-center items-center gap-8 bg-black backdrop-blur bg-opacity-90 [@supports(backdrop-filter:blur(0))]:bg-opacity-70 z-10">
|
|
<video
|
|
controls
|
|
autoplay
|
|
onended={() => setShowTrailer(false)}
|
|
>
|
|
<source src="/RaeppliTrailer.mp4" type="video/mp4" />
|
|
</video>
|
|
<button
|
|
class="btn btn-accent btn-xl gap-2"
|
|
onClick={() => setShowTrailer(false)}
|
|
>
|
|
<CloseIcon />
|
|
Schliessen
|
|
</button>
|
|
</div>
|
|
)}
|
|
</Show>
|
|
|
|
<div class="mt-24 text-base grid grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 text-center gap-y-3 md:gap-y-5 gap-x-2 lg:gap-x-8 items-center justify-center">
|
|
<button
|
|
class="shadow-md btn btn-secondary hover:ring-2 ring-offset-2 ring-secondary bg-pink-600 hover gap-2 whitespace-normal flex-nowrap"
|
|
onClick={() => setShowTrailer(true)}
|
|
>
|
|
<VideoIcon />
|
|
<span>
|
|
Trailer <span class="hidden xl:inline">anschauen</span>
|
|
</span>
|
|
</button>
|
|
<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"
|
|
>
|
|
<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 unterstützen
|
|
</a>
|
|
<a
|
|
class="link text-secondary-focus hover:text-secondary"
|
|
href="#welcome-opensource"
|
|
>
|
|
Open Source
|
|
</a>
|
|
<a
|
|
class="link text-secondary-focus hover:text-secondary"
|
|
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"
|
|
id="welcome-social"
|
|
>
|
|
<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 class="text-xl font-light lead">
|
|
Weil moderne Rechnungsstellung für alle Menschen möglich sein
|
|
sollte!
|
|
</p>
|
|
<p>
|
|
Jeder Selbstständige kommt wohl irgendwann an den Punkt, dass er
|
|
eine Rechnung schreiben muss. Doch eine moderne Rechnung zu
|
|
schreiben, die heutige Erwartungen erfüllt, ist gar nicht so
|
|
einfach und braucht viel Zeit. So werden viele Selbstständige zum
|
|
Einsatz von komplexer und oftmals teurer "ERP"-Software oder zu
|
|
Outsourcing gedrängt. Menschen die sich mit wenig Startkapital
|
|
selbstständig machen wollen, stehen vor einer schwierigen Wahl.
|
|
</p>
|
|
<p>
|
|
Eines der wesentlichen Ziele von{" "}
|
|
<a href="#welcome-lufrai">Lufrai</a> ist, Menschen dabei zu
|
|
unterstützen aus eigener Kraft unabhängig zu werden und ihrem
|
|
eigenen Lebenssinn zu folgen.{" "}
|
|
<strong>
|
|
<i>Befreie deine Stimme!</i>
|
|
</strong>
|
|
</p>
|
|
<p>
|
|
Räppli soll ein Beweis dafür darstellen, dass Selbstlosigkeit auch
|
|
ohne teure, zentralisierte, staatliche Lösungen möglich ist. Der
|
|
gesamte Quellcode von Räppli steht offen verfügbar.{" "}
|
|
<a href="#welcome-opensource">Erfahre mehr</a>
|
|
</p>
|
|
<p>
|
|
Zu guter Letzt ist Räppli auch eine Referenzarbeit. Sie soll
|
|
zeigen was ich (Katja) als Web-Entwicklerin drauf habe. Denn im
|
|
mündlichen Gespräch verkaufe ich meine Fähigkeiten leider wie eine
|
|
absolute Niete und wirke mit einer Grösse von 1.9 Metern etwas
|
|
abschreckend. Verkauf ist nicht meine Stärke.
|
|
</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={externalLinkClass}
|
|
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-1"
|
|
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 class="text-xl font-light lead">
|
|
Danke dass du das Projekt und damit auch den Fortbestand von
|
|
Lufrai unterstützen möchtest!
|
|
</p>
|
|
<h3>Teilen</h3>
|
|
<p>
|
|
<a href="#welcome-social">Teile</a> Räppi unter deinen Freunden
|
|
und Bekannten was das Zeug hält! Lufrai investiert kein Geld in
|
|
teures Marketing, aus der Überzeugung, dass es auf unserer Welt
|
|
bereits genug davon gibt.
|
|
</p>
|
|
<h3>Spenden</h3>
|
|
<p>
|
|
Räppli 1.0 wurde mit einem Aufwand von fast 4 Wochen entwickelt, "
|
|
<a href="#welcome-lufrai">Lufrai - Katja Lutz</a>" hat somit etwa
|
|
18'000 CHF in die Verwirklichung von diesem Projekt investiert.{" "}
|
|
<strong>
|
|
Herzlichen Dank, dass du dich daran beteiligen möchtest!
|
|
</strong>
|
|
</p>
|
|
<a
|
|
href="https://lufrai.org/spenden/"
|
|
class={`btn btn-sm btn-secondary bg-purple-600 border-purple-600 ${externalLinkClass}`}
|
|
{...externalLink}
|
|
>
|
|
Kontoinformationen <ExternalLinkIcon />
|
|
</a>
|
|
<h3>Mitwirkung / Feedback</h3>
|
|
<p>
|
|
Für Feedbacks, bitte beachte den entsprechenden{" "}
|
|
<a href="#welcome-feedback" onClick={onClickFocus}>
|
|
FAQ-Abschnitt
|
|
</a>
|
|
.
|
|
</p>
|
|
<p>
|
|
Du bist ein Web-Entwickler, verstehst Javascript / Typescript wie
|
|
deine Westentasche und möchtest Räppli verbessern? Hurra! Bitte
|
|
schaue dir den <a href="#welcome-opensource">Open Source</a>{" "}
|
|
Abschnitt an.
|
|
</p>
|
|
</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{" "}
|
|
<ExternalLink href="https://git.lufrai.com/rappli/rappli/src/branch/master/LICENSE">
|
|
MIT
|
|
</ExternalLink>
|
|
.
|
|
<a
|
|
class="btn btn-sm inline-flex ml-3 items-center gap-1"
|
|
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={externalLinkClass}
|
|
href="https://www.solidjs.com/"
|
|
{...externalLink}
|
|
>
|
|
Solid
|
|
<ExternalLinkIcon />
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a
|
|
class={externalLinkClass}
|
|
href="https://tailwindcss.com/"
|
|
{...externalLink}
|
|
>
|
|
Tailwind CSS
|
|
<ExternalLinkIcon />
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a
|
|
class={externalLinkClass}
|
|
href="https://daisyui.com/"
|
|
{...externalLink}
|
|
>
|
|
daisyUI
|
|
<ExternalLinkIcon />
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a
|
|
class={externalLinkClass}
|
|
href="https://sortablejs.github.io/Sortable/"
|
|
{...externalLink}
|
|
>
|
|
Sortable
|
|
<ExternalLinkIcon />
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a
|
|
class={externalLinkClass}
|
|
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 (Katja) mich bei der Community von{" "}
|
|
<a
|
|
class={externalLinkClass}
|
|
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 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>
|
|
<p>
|
|
Mein herzlicher Dank gilt zudem auch{" "}
|
|
<ExternalLink href="https://www.kohei.dev/en-us">
|
|
Kohei Asai
|
|
</ExternalLink>{" "}
|
|
<small>
|
|
(
|
|
<ExternalLink href="https://twitter.com/axross_">
|
|
Twitter
|
|
</ExternalLink>
|
|
)
|
|
</small>
|
|
, welcher mir sehr grosszügig seine Rechte am Paketnamen "rappli"
|
|
auf{" "}
|
|
<ExternalLink href="https://npmjs.com">npmjs.com</ExternalLink>{" "}
|
|
übertragen hat.
|
|
</p>
|
|
</section>
|
|
|
|
<section class="mt-16">
|
|
<h2 id="welcome-faq">Häufig gestellte Fragen</h2>
|
|
<div class="text-black">
|
|
<AccordionItem
|
|
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="text-title-border"
|
|
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={externalLinkClass}
|
|
href="https://t3n.de/news/tcp-ip-internet-grundlagen-755667/"
|
|
{...externalLink}
|
|
>
|
|
hier
|
|
<ExternalLinkIcon />
|
|
</a>{" "}
|
|
wie das Internet funktioniert.
|
|
</li>
|
|
</ul>
|
|
</AccordionItem>
|
|
|
|
<AccordionItem
|
|
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" />!
|
|
</AccordionItem>
|
|
|
|
<AccordionItem label="3. Werden meine Nutzungsdaten zu Marketingzwecken gesammelt?">
|
|
<strong>Nein</strong>, vorausgesetzt du verwendest das
|
|
offizielle Räppli über rappli.ch und keine modizifierte Version.
|
|
Lufrai legt Wert auf deine Privatsphäre. Es werden keine
|
|
Nutzungsdaten an Google, Facebook oder ähnliche Riesen
|
|
übermittelt.
|
|
</AccordionItem>
|
|
|
|
<AccordionItem
|
|
id="welcome-feedback"
|
|
label={
|
|
<>
|
|
4. Ich habe einen Anpassungswunsch!
|
|
<br />
|
|
Kann die App bitte gratis angepasst werden?
|
|
</>
|
|
}
|
|
alignCenter={false}
|
|
>
|
|
Räppli ist <a href="#welcome-opensource">Open Source</a> und
|
|
wird grundsätzlich von <a href="#welcome-lufrai">Lufrai</a>{" "}
|
|
weiterentwickelt. Für eine optimale Weiterentwicklung ist dein
|
|
Feedback sehr wichtig und ich (Katja) bin für dein Feedback und
|
|
deine Ideen dankbar! Jedoch besteht keine Garantie dafür, dass
|
|
jedes Feedback beantwortet / umgesetzt werden kann, die
|
|
Entwicklung ist aufwändig und kein Wunschkonzert. Kontaktiere
|
|
Lufrai auf{" "}
|
|
<a
|
|
href="https://lufrai.org/impressum/"
|
|
class={externalLinkClass}
|
|
{...externalLink}
|
|
>
|
|
Kontakt <ExternalLinkIcon />
|
|
</a>{" "}
|
|
oder erstelle ein Issue auf dem{" "}
|
|
<a href="#welcome-opensource">Git-Repository</a>.
|
|
</AccordionItem>
|
|
|
|
<AccordionItem
|
|
id="welcome-agile"
|
|
label={
|
|
<div>
|
|
5. Wie funktionieren <strong>Agile Positionen</strong>?
|
|
</div>
|
|
}
|
|
alignCenter={false}
|
|
>
|
|
<p class="!mt-0 text-lg font-light lead">
|
|
Agile Positionen können dir dabei helfen, Arbeitsaufwand
|
|
(Zeit) zu schätzen.
|
|
</p>
|
|
<p>
|
|
Der Preis von agilen Positionen wird aus Story Points, einem
|
|
Umrechnungssatz "Stunden pro Story Point", dem Risiko Faktor
|
|
und dem Stundensatz ("Einzelpreis") berechnet.
|
|
</p>
|
|
<h3>Was sind Story Points?</h3>
|
|
<p>
|
|
Story Points sind relative Punktzahlen. Weise all deinen
|
|
Positionen Story Points zu, die im Verhältnis zueinander
|
|
stimmen. Vergleiche dazu deine Positionen und überlege dir,
|
|
welche davon schwieriger sind und welche einfacher. Wie viel
|
|
schwieriger ist eine Position (Arbeitsaufwand) im Vergleich zu
|
|
einer anderen?
|
|
</p>
|
|
<p>
|
|
Nachdem du die Story Points zugewiesen hast, kannst du dir
|
|
überlegen, wie viel Stunden ein einzelner Story Point wert
|
|
ist, trage diesen in den "Dokument"-Einstellungen unter
|
|
"Stunden pro Story Point" ein.
|
|
</p>
|
|
<a
|
|
href="https://www.agile-academy.com/de/product-owner/was-sind-story-points/"
|
|
class={externalLinkClass}
|
|
{...externalLink}
|
|
>
|
|
Erfahre mehr über Story Points
|
|
<ExternalLinkIcon />
|
|
</a>
|
|
<h3>Was ist der Risiko Faktor?</h3>
|
|
<p>
|
|
Aufwandschätzungen sind eine riskante Angelegenheit. Räppli
|
|
erlaubt dir deshalb für den geschätzten, optimalen Fall "Story
|
|
Points Minimum" und für den schlimmsten Fall "Story Points
|
|
Maximum" anzugeben.
|
|
<br />
|
|
<strong>
|
|
Der Risiko Faktor wird in den "Dokument"-Einstellungen
|
|
eingegeben und er entscheidet über die Gewichtung der
|
|
minimalen und maximalen Story Points:
|
|
</strong>
|
|
</p>
|
|
<ul class="mb-8">
|
|
<li>
|
|
Risiko Faktor von <span class="underline">0%</span>: Es
|
|
existiert kein Risiko, <strong>nur die minimalen</strong>{" "}
|
|
Story Points werden beachtet!
|
|
</li>
|
|
<li>
|
|
Risiko Faktor von <span class="underline">70%</span>:{" "}
|
|
<strong>30% von den Minimalen</strong>, und{" "}
|
|
<strong>70% von den maximalen</strong> Story Points werden
|
|
beachtet.
|
|
</li>
|
|
<li>
|
|
Risiko Faktor von <span class="underline">100%</span>: Der
|
|
Worst-Case ist alternativlos,{" "}
|
|
<strong>nur die maximalen</strong> Story Points werden
|
|
beachtet!
|
|
</li>
|
|
</ul>
|
|
|
|
<AgileCalculator />
|
|
</AccordionItem>
|
|
|
|
<AccordionItem
|
|
label={"6. 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>
|
|
</AccordionItem>
|
|
|
|
<AccordionItem
|
|
label={
|
|
<div>
|
|
7. Können Fliesstexte formatiert werden?
|
|
<br />
|
|
Was ist <strong>Markdown</strong>?
|
|
</div>
|
|
}
|
|
alignCenter={false}
|
|
>
|
|
<p class="!mt-0 mb-1">
|
|
Du kannst deinen Text formatieren, indem du ihn mit ganz
|
|
bestimmten Zeichen versiehst! Welche Zeichen das sind, wird
|
|
durch den "Markdown"-Standard definiert.
|
|
</p>
|
|
<p class="mt-1">
|
|
Erfahre{" "}
|
|
<a
|
|
href={markdownHelpUrl}
|
|
class={externalLinkClass}
|
|
{...externalLink}
|
|
>
|
|
hier <ExternalLinkIcon />
|
|
</a>{" "}
|
|
wie Markdown im Detail funktioniert!
|
|
</p>
|
|
|
|
<strong>Kurzbeispiel:</strong>
|
|
<div class="prose-sm">
|
|
<div class="grid grid-cols-2">
|
|
<p>Dein Text:</p>
|
|
<pre>
|
|
{["- *Pizza*", "- **Gemüse**", "- ***Salat***"].join(
|
|
"\n"
|
|
)}
|
|
</pre>
|
|
<p>Ausgabe:</p>
|
|
<div class="border border-slate-200">
|
|
<ul>
|
|
<li>
|
|
<i>Pizza</i>
|
|
</li>
|
|
<li>
|
|
<strong>Gemüse</strong>
|
|
</li>
|
|
<li>
|
|
<i>
|
|
<strong>Salat</strong>
|
|
</i>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</AccordionItem>
|
|
|
|
<AccordionItem
|
|
id="welcome-local-installation"
|
|
label={<div>8. 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={externalLinkClass}
|
|
href="https://nodejs.org/en/"
|
|
{...externalLink}
|
|
>
|
|
nodejs {">"}= 18 <ExternalLinkIcon />
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
<h3>Installation Räppli</h3>
|
|
<p>Führe folgenden Befehl aus:</p>
|
|
<pre>npm install -g rappli</pre>
|
|
<h3>Räppli starten</h3>
|
|
<p>Führe folgenden Befehl aus, um Räppli zu starten</p>
|
|
<pre>rappli</pre>
|
|
<p>
|
|
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.
|
|
</p>
|
|
</AccordionItem>
|
|
|
|
<AccordionItem
|
|
label={
|
|
<div>
|
|
9. 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>
|
|
</AccordionItem>
|
|
|
|
<AccordionItem
|
|
label={<div>10. 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>
|
|
</AccordionItem>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
<div class="mt-8 xl:mt-14 relative flex justify-center items-center gap-2 sm:gap-5 xl:gap-10">
|
|
<a
|
|
href="#welcome-patron"
|
|
class="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 lg: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>
|
|
<div
|
|
class="static md:relative lg:hidden tooltip tooltip-error"
|
|
data-tip="Bitte verwende einen Laptop oder Computer mit einer
|
|
Mindestbreite von 1024 Pixeln!"
|
|
>
|
|
<div class="btn hover:!bg-white hover:!text-black/50 btn-outline flex gap-2 transition-all shadow-indigo-300 hover:shadow-red-300 hover:ring-2 ring-offset-2 ring-red-400">
|
|
<WarningIcon /> Loslegen
|
|
</div>
|
|
</div>
|
|
<button
|
|
class="btn relative border-red-600 bg-red-500 hover:bg-red-600 hover:border-pink-700 hover:ring-2 ring-offset-2 ring-pink-500 shadow-red-300 shadow-lg lg:shadow-md lg:shadow-red-300 hover:shadow-red-400 tooltip tooltip-error stroke-1 stroke-white"
|
|
onClick={() => {
|
|
setLikeCount(likeCount() + 1);
|
|
increaseCount_();
|
|
}}
|
|
data-tip={(likes() || 0) + likeCount()}
|
|
>
|
|
<div
|
|
class="flex justify-center transition-transform"
|
|
style={{
|
|
transform: `scale(${clamp(0.9, 0.9 + likeCount() / 12, 1.3)})`,
|
|
}}
|
|
>
|
|
<Show when={likeCount() === 0}>
|
|
<FavoriteIcon />
|
|
</Show>
|
|
<Show when={likeCount() === 1}>
|
|
<FavoriteHalfIcon />
|
|
</Show>
|
|
<Show when={likeCount() > 1}>
|
|
<FavoriteFilledIcon />
|
|
</Show>
|
|
</div>
|
|
<span aria-disabled="true" class="text-xs text-orange-200 font-black">
|
|
{Intl.NumberFormat("en-US", {
|
|
notation: "compact",
|
|
maximumFractionDigits: 1,
|
|
}).format((likes() || 0) + likeCount())}
|
|
</span>
|
|
|
|
<For each={confetti}>
|
|
{(v) => (
|
|
<Show when={v}>
|
|
<ConfettiExplosion
|
|
stageHeight={1500}
|
|
duration={confettiDuration}
|
|
force={1}
|
|
shouldDestroyAfterDone={true}
|
|
/>
|
|
</Show>
|
|
)}
|
|
</For>
|
|
</button>
|
|
<button
|
|
onClick={() => setLocalState("showWelcome", false)}
|
|
class="hidden lg:flex btn btn-lg btn-primary gap-2 transition-all shadow-lg shadow-indigo-300 hover:shadow-indigo-500 hover:ring-2 ring-offset-2 ring-indigo-500"
|
|
>
|
|
<LaunchIcon />
|
|
Loslegen
|
|
</button>
|
|
</div>
|
|
<a
|
|
href="https://git.lufrai.com/rappli/rappli/src/branch/master/CHANGELOG.md#changelog"
|
|
class="hover:underline absolute bottom-8 right-8 text-xs text-black opacity-60 flex gap-3"
|
|
{...externalLink}
|
|
>
|
|
<span>Version: {__APP_VERSION__}</span>
|
|
<span>{getDisplayDate(new Date(__BUILD_TIME__))}</span>
|
|
</a>
|
|
</Modal>
|
|
);
|
|
};
|
|
|
|
export default WelcomeModal;
|