summaryrefslogtreecommitdiffstats
path: root/src/components
diff options
context:
space:
mode:
authorMaksymilian Jopek <maks@jopek.eu>2025-04-28 18:03:28 +0200
committerMaksymilian Jopek <maks@jopek.eu>2025-04-28 18:03:28 +0200
commit0539d6963ebedd554cd3cd36b6c94df9659d6a6b (patch)
tree9408c7928d0725b114d0adfa811ba2bb3859f495 /src/components
downloadrustberry-frontend-0539d6963ebedd554cd3cd36b6c94df9659d6a6b.tar.gz
rustberry-frontend-0539d6963ebedd554cd3cd36b6c94df9659d6a6b.tar.zst
rustberry-frontend-0539d6963ebedd554cd3cd36b6c94df9659d6a6b.zip
Initial commitHEADmaster
Simple frontend version with mock API
Diffstat (limited to 'src/components')
-rw-r--r--src/components/Button.module.css29
-rw-r--r--src/components/Button.tsx57
-rw-r--r--src/components/Timer.module.css63
-rw-r--r--src/components/Timer.tsx178
4 files changed, 327 insertions, 0 deletions
diff --git a/src/components/Button.module.css b/src/components/Button.module.css
new file mode 100644
index 0000000..7041eaf
--- /dev/null
+++ b/src/components/Button.module.css
@@ -0,0 +1,29 @@
+.btn:last-child {
+ margin-bottom: 0;
+}
+.generic-button {
+ display: grid;
+ place-items: center;
+ border: 3px solid #222;
+ color: white;
+ border-radius: 15px;
+ font-size: 1.6em;
+ width: 100%;
+ transition: transform 0.05s;
+ user-select: none;
+}
+.generic-button:enabled:active {
+ transform: scale(0.8);
+ box-shadow: inset 0px 0px 5px 0px black;
+}
+.generic-button:disabled {
+ filter: grayscale(0.8);
+}
+.btn {
+ composes: generic-button;
+ --page-size-without-margins: (
+ 100svh - (var(--rows-count) + 1) * var(--btn-gap)
+ );
+ --btn-height: calc(var(--page-size-without-margins) / var(--rows-count));
+ height: var(--btn-height);
+}
diff --git a/src/components/Button.tsx b/src/components/Button.tsx
new file mode 100644
index 0000000..031fc73
--- /dev/null
+++ b/src/components/Button.tsx
@@ -0,0 +1,57 @@
+import styles from "./Button.module.css";
+import { createEffect, createSignal } from "solid-js";
+
+enum BtnType {
+ click = 0,
+ switch = 1,
+}
+export type Button =
+ | {
+ type: BtnType.click;
+ name: string;
+ color?: string;
+ }
+ | {
+ type: BtnType.switch;
+ name: string;
+ };
+
+export default function Button(props: Button) {
+ const [value, setValue] = createSignal(false);
+ const [bgColor, setBgColor] = createSignal<string>();
+
+ createEffect(() => {
+ if (props.type === BtnType.click) {
+ setBgColor(value() ? "pink" : "blue");
+ } else {
+ setBgColor(value() ? "green" : "red");
+ }
+ });
+
+ function onClick() {
+ if (props.type === BtnType.click) {
+ if (value() === true) {
+ return;
+ }
+ setValue(true);
+ setTimeout(() => {
+ setValue(false);
+ }, 3000);
+ } else {
+ setValue((old) => !old);
+ }
+ }
+
+ return (
+ <button
+ on:click={onClick}
+ class={styles.btn}
+ style={{ "background-color": bgColor() }}
+ >
+ {
+ //btnText()
+ }
+ {props.name}
+ </button>
+ );
+}
diff --git a/src/components/Timer.module.css b/src/components/Timer.module.css
new file mode 100644
index 0000000..b748c9e
--- /dev/null
+++ b/src/components/Timer.module.css
@@ -0,0 +1,63 @@
+.cont {
+ h2 {
+ margin-top: 0.4em;
+ margin-bottom: 1rem;
+ }
+ input {
+ width: 2em;
+ font-size: 1.5em;
+ padding: 0.3em;
+ }
+}
+.custom-option {
+ display: grid;
+ align-items: center;
+ grid-template-columns: 1fr 2fr 1fr;
+ margin-bottom: 1em;
+}
+.units {
+ display: inline-flex;
+ gap: 1.5em;
+ span {
+ font-size: 2em;
+ }
+}
+.not-selected-unit {
+ font-weight: lighter;
+}
+.selected-unit {
+ font-weight: bold;
+ text-decoration: underline;
+}
+.darkgray {
+ color: darkgray;
+}
+.custom-option-btn {
+ composes: generic-button from "./Button.module.css";
+ background-color: green;
+ height: 2em;
+}
+.extra-stop-btn {
+ composes: generic-button from "./Button.module.css";
+ background-color: red;
+ padding: 0.5em;
+ margin-top: 0.5em;
+}
+
+.time-left {
+ margin-bottom: 1em;
+ h2 {
+ display: inline;
+ color: lightblue;
+ }
+}
+
+.options {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 1rem;
+}
+.option {
+ composes: generic-button from "./Button.module.css";
+ height: calc((100vw - 2rem) / 3 / 1.5);
+}
diff --git a/src/components/Timer.tsx b/src/components/Timer.tsx
new file mode 100644
index 0000000..61a98f5
--- /dev/null
+++ b/src/components/Timer.tsx
@@ -0,0 +1,178 @@
+import { createSignal, For, Match, Show, Switch } from "solid-js";
+import styles from "./Timer.module.css";
+
+export const TIME_UNITS = ["s", "m", "h", "d"] as const;
+export type Timer = {
+ title: string;
+ options: {
+ time: number;
+ unit: (typeof TIME_UNITS)[number];
+ }[];
+ backgroundColor: string;
+ onTill: number | null;
+ secondsLeftText: string;
+ extraStopActions: { title: string }[];
+};
+
+export default function Timer(props: Timer) {
+ const [onTill, setOnTill] = createSignal(props.onTill);
+ const [unit, setUnit] = createSignal<(typeof TIME_UNITS)[number]>(
+ TIME_UNITS[1],
+ );
+ const [formattedTime, setFormattedTime] = createSignal("");
+ const [disabled, setDisabled] = createSignal(onTill() !== null);
+ const [customValue, setCustomValue] = createSignal<number | "">("");
+
+ let _intervalId = null;
+ function toggleAutoUpdate() {
+ if (_intervalId === null) {
+ updateTime();
+ _intervalId = setInterval(updateTime, 1000);
+ } else {
+ clearInterval(_intervalId);
+ _intervalId = null;
+ }
+ }
+ if (props.onTill !== null) {
+ toggleAutoUpdate();
+ }
+ function updateTime() {
+ const s = Math.floor(onTill() - Date.now() / 1000);
+ const days = Math.floor(s / (24 * 3600));
+ const hours = Math.floor((s % (24 * 3600)) / 3600);
+ const minutes = Math.floor((s % 3600) / 60);
+ const seconds = s % 60;
+
+ let text = "";
+ if (days > 0) {
+ text += days + "d : ";
+ }
+ if (hours > 0 || text.length > 0) {
+ text += hours + "h : ";
+ }
+ if (minutes > 0 || text.length > 0) {
+ text += minutes + "m : ";
+ }
+ if (seconds > 0 || text.length > 0) {
+ text += seconds + "s";
+ }
+ setFormattedTime(text);
+ }
+ function startStopClick() {
+ const val = customValue();
+ if (disabled() === false && val !== "") {
+ optionClick({ time: val, unit: unit() });
+ } else if (disabled() === true) {
+ setDisabled(false);
+ setOnTill(null);
+ toggleAutoUpdate();
+ }
+ }
+
+ function optionClick(option: Timer["options"][number]) {
+ setDisabled(true);
+ let newOnTill = Math.floor(Date.now() / 1000);
+ if (option.unit === "s") {
+ newOnTill += option.time;
+ } else if (option.unit === "m") {
+ newOnTill += option.time * 60;
+ } else if (option.unit === "h") {
+ newOnTill += option.time * 3600;
+ } else {
+ newOnTill += option.time * 3600 * 24;
+ }
+ setOnTill(newOnTill);
+ toggleAutoUpdate();
+ }
+
+ function extraStopClick(act: Timer["extraStopActions"][number]) {
+ // Some api call for the extra
+ startStopClick();
+ }
+ return (
+ <div class={styles.cont}>
+ <h2>{props.title}</h2>
+
+ <div class={styles.customOption}>
+ <input
+ type="number"
+ value={customValue()}
+ on:input={(e) =>
+ setCustomValue(e.target.value ? parseFloat(e.target.value) : "")
+ }
+ disabled={disabled()}
+ min="1"
+ />
+ <div class={styles.units}>
+ <For each={TIME_UNITS}>
+ {(u) => (
+ <span
+ on:click={() => (disabled() ? null : setUnit(u))}
+ classList={{
+ [styles.selectedUnit]: unit() === u,
+ [styles.notSelectedUnit]: unit() !== u,
+ [styles.darkgray]: disabled() === true,
+ }}
+ >
+ {u}
+ </span>
+ )}
+ </For>
+ </div>
+ <button
+ class={styles.customOptionBtn}
+ on:click={startStopClick}
+ disabled={
+ disabled()
+ ? false
+ : customValue() === "" || (customValue() as number) <= 0
+ }
+ style={{ "background-color": disabled() ? "red" : "green" }}
+ >
+ {disabled() ? "Wyłącz" : "Włącz"}
+ </button>
+ {/*<Show when={disabled() === true}>*/}
+ <For each={props.extraStopActions}>
+ {(act) => (
+ <button
+ disabled={disabled() === false}
+ on:click={[extraStopClick, act]}
+ class={styles.extraStopBtn}
+ >
+ {act.title}
+ </button>
+ )}
+ </For>
+ {/*</Show>*/}
+ </div>
+
+ <div class={styles.timeLeft}>
+ <Switch>
+ <Match when={onTill() === null}>
+ <span>Wyłączone</span>
+ <h2> </h2>
+ </Match>
+ <Match when={onTill() > 0}>
+ <span>{props.secondsLeftText} </span>
+ <h2>{formattedTime()}</h2>
+ </Match>
+ </Switch>
+ </div>
+
+ <div class={styles.options}>
+ <For each={props.options}>
+ {(option) => (
+ <button
+ class={styles.option}
+ on:click={[optionClick, option]}
+ style={{ "background-color": props.backgroundColor }}
+ disabled={disabled()}
+ >
+ {option.time} {option.unit}
+ </button>
+ )}
+ </For>
+ </div>
+ </div>
+ );
+}