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

438 lines
16 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 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 { getDisplayDate, getHost, 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>
);
return (
<Modal open={isOpen()}>
<div class="hidden xl:block">
<ModalCloseButton onClick={() => setLocalState("showWelcome", false)} />
</div>
<div class="max-h-[50vh] overflow-y-auto px-2">
<div class="min-h-[50vh] 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"
target="_blank"
rel="noopener"
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"
>
<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 flex items-center gap-2"
href="#welcome-opensource"
>
Open Source
</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"
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">
<section>
<h1 id="welcome-quickstart">Einleitung</h1>
<p>{description}</p>
</section>
<section class="mt-16">
<h1 id="welcome-why-free">
Wieso ist <span class="text-swiss-red">Räppli</span> komplett{" "}
<span class="text-swiss-red">kostenlos</span>?
</h1>
<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">
<h1 id="welcome-lufrai">
Was ist <span class="text-lufrai-primary">Lufrai</span>?
</h1>
<p>
Quis occaecat pariatur laborum do ad esse. Mollit excepteur duis
nulla proident nostrud tempor ad ullamco. Amet id magna aute esse
tempor incididunt pariatur veniam ipsum qui. Sunt laboris in
laborum reprehenderit qui sint consectetur nostrud excepteur
proident proident laboris qui dolor ea. Reprehenderit tempor elit
consequat dolore quis ad voluptate consequat. Eiusmod ad quis
dolore dolore ea ipsum commodo eu aliquip veniam. Consequat
pariatur est do sint nisi duis enim tempor occaecat elit non.
</p>
</section>
<section class="mt-16">
<h1 id="welcome-opensource">Open Source</h1>
<p>
Quis occaecat pariatur laborum do ad esse. Mollit excepteur duis
nulla proident nostrud tempor ad ullamco. Amet id magna aute esse
tempor incididunt pariatur veniam ipsum qui. Sunt laboris in
laborum reprehenderit qui sint consectetur nostrud excepteur
proident proident laboris qui dolor ea. Reprehenderit tempor elit
consequat dolore quis ad voluptate consequat. Eiusmod ad quis
dolore dolore ea ipsum commodo eu aliquip veniam. Consequat
pariatur est do sint nisi duis enim tempor occaecat elit non.
</p>
<a
class="btn btn-sm flex items-center gap-2"
target="_blank"
rel="noopener"
href="https://git.lufrai.com/rappli/rappli"
>
Git-Repository
<ExternalLinkIcon />
</a>
</section>
<section class="mt-16">
<h1 id="welcome-patron">Das Projekt unterstützen</h1>
<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>
<h2>Spende</h2>
<h2>Mitwirkung</h2>
</section>
<section class="mt-16">
<h1 id="welcome-faq">Häufig gestellte Fragen</h1>
<div class="text-black">
<AcordionItem
label={"1. Wo werden meine Daten gespeichert?"}
alignCenter={false}
>
test
</AcordionItem>
<AcordionItem
label="2. Ich habe vergessen zu speichern, was nun?!"
alignCenter={false}
>
test
</AcordionItem>
<AcordionItem
label={
<>
3. Ich habe einen Anpassungswunsch!
<br />
Kann die App bitte gratis angepasst werden?
</>
}
alignCenter={false}
>
test
</AcordionItem>
<AcordionItem
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}
>
test
</AcordionItem>
<AcordionItem
label={
<div>
6. Können Fliesstexte formatiert werden?
<br />
Was ist <strong>Markdown</strong>?
</div>
}
alignCenter={false}
>
test
</AcordionItem>
<AcordionItem
label={<div>7. Kann Räppli lokal installiert werden?</div>}
alignCenter={false}
>
test
</AcordionItem>
<AcordionItem
label={
<div>
8. Wie präzise wurden die SIX QR-Rechnung Vorgaben
umgesetzt?
</div>
}
alignCenter={false}
>
test
</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;