Login page design
This commit is contained in:
@@ -4,7 +4,8 @@ import { RequestError } from "./RequestError";
|
|||||||
import { Database } from "./services/database";
|
import { Database } from "./services/database";
|
||||||
import { Request } from "./services/request";
|
import { Request } from "./services/request";
|
||||||
import { brotliCompress } from "node:zlib";
|
import { brotliCompress } from "node:zlib";
|
||||||
import { AccessLog } from "common";
|
import { AccessLog } from "common/db";
|
||||||
|
import { LoginRequest, LoginResponse } from "common/api";
|
||||||
|
|
||||||
const match = (method: string, ...pattern: readonly string[]) => Effect.gen(function* () {
|
const match = (method: string, ...pattern: readonly string[]) => Effect.gen(function* () {
|
||||||
|
|
||||||
@@ -82,10 +83,7 @@ export const app = pipe(
|
|||||||
|
|
||||||
if (yield* match("POST", "login")) {
|
if (yield* match("POST", "login")) {
|
||||||
|
|
||||||
const body = yield* requestJson(S.Struct({
|
const body = yield* requestJson(LoginRequest);
|
||||||
username: S.NonEmptyString,
|
|
||||||
password: S.NonEmptyString,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const user = yield* pipe(
|
const user = yield* pipe(
|
||||||
db.getUserByUsername(body.username),
|
db.getUserByUsername(body.username),
|
||||||
@@ -99,12 +97,17 @@ export const app = pipe(
|
|||||||
|
|
||||||
const sessionId = yield* db.createSession(user.userId);
|
const sessionId = yield* db.createSession(user.userId);
|
||||||
|
|
||||||
const responseData = {
|
const responseData = LoginResponse.make({
|
||||||
userId: user.userId,
|
userId: user.userId,
|
||||||
username: user.username,
|
username: user.username,
|
||||||
admin: user.admin,
|
admin: user.admin,
|
||||||
};
|
});
|
||||||
const responseJson = JSON.stringify(responseData);
|
|
||||||
|
const responseJson = yield* pipe(
|
||||||
|
responseData,
|
||||||
|
S.encode(LoginResponse),
|
||||||
|
Effect.map(JSON.stringify),
|
||||||
|
);
|
||||||
const responseArray = new TextEncoder().encode(responseJson);
|
const responseArray = new TextEncoder().encode(responseJson);
|
||||||
|
|
||||||
const expiresAt = DateTime.addDuration(yield* DateTime.now, Duration.days(7));
|
const expiresAt = DateTime.addDuration(yield* DateTime.now, Duration.days(7));
|
||||||
|
|||||||
13
packages/common/src/api.ts
Normal file
13
packages/common/src/api.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { Schema as S } from "@effect/schema";
|
||||||
|
import { UserId } from "common";
|
||||||
|
|
||||||
|
export const LoginRequest = S.Struct({
|
||||||
|
username: S.NonEmptyString,
|
||||||
|
password: S.NonEmptyString,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const LoginResponse = S.Struct({
|
||||||
|
userId: UserId,
|
||||||
|
username: S.NonEmptyString,
|
||||||
|
admin: S.Boolean,
|
||||||
|
});
|
||||||
67
packages/common/src/db.ts
Normal file
67
packages/common/src/db.ts
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import { Schema as S } from "@effect/schema";
|
||||||
|
import { AttachmentId, BooleanFromNumber, PieceId, RequestId, SessionId, Sha256, UserId } from "common";
|
||||||
|
import { Brand as B, pipe } from "effect";
|
||||||
|
|
||||||
|
export const SystemInformation = S.Struct({
|
||||||
|
createdBy: S.Union(UserId, S.Null),
|
||||||
|
createdAt: S.DateTimeUtc,
|
||||||
|
modifiedBy: S.Union(UserId, S.Null),
|
||||||
|
modifiedAt: S.DateTimeUtc,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type SystemInformation = typeof SystemInformation.Type;
|
||||||
|
|
||||||
|
// --- TABLES ------------------------------------------------------------------
|
||||||
|
|
||||||
|
export const AccessLog = S.Struct({
|
||||||
|
timestamp: S.DateTimeUtc,
|
||||||
|
requestId: RequestId,
|
||||||
|
method: S.NonEmptyString,
|
||||||
|
pathname: S.NonEmptyString,
|
||||||
|
query: S.parseJson(S.Record({
|
||||||
|
key: S.String,
|
||||||
|
value: S.String,
|
||||||
|
})),
|
||||||
|
ip: S.Union(S.NonEmptyString, S.Null),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Attachment = pipe(
|
||||||
|
S.Struct({
|
||||||
|
attachmentId: AttachmentId,
|
||||||
|
pieceId: PieceId,
|
||||||
|
sha256: Sha256,
|
||||||
|
filename: S.NonEmptyString,
|
||||||
|
mediaType: S.NonEmptyString,
|
||||||
|
}),
|
||||||
|
S.extend(SystemInformation),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const Piece = pipe(
|
||||||
|
S.Struct({
|
||||||
|
pieceId: PieceId,
|
||||||
|
name: S.NonEmptyString,
|
||||||
|
composer: S.Union(S.NonEmptyString, S.Null),
|
||||||
|
lyricist: S.Union(S.NonEmptyString, S.Null),
|
||||||
|
arranger: S.Union(S.NonEmptyString, S.Null),
|
||||||
|
}),
|
||||||
|
S.extend(SystemInformation),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const Session = S.Struct({
|
||||||
|
sessionId: SessionId,
|
||||||
|
userId: UserId,
|
||||||
|
expiresAt: S.DateTimeUtc,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const User = S.Struct({
|
||||||
|
userId: UserId,
|
||||||
|
username: S.NonEmptyString,
|
||||||
|
password: S.NonEmptyString,
|
||||||
|
admin: BooleanFromNumber,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type AccessLog = typeof AccessLog.Type;
|
||||||
|
export type Attachment = typeof Attachment.Type;
|
||||||
|
export type Piece = typeof Piece.Type;
|
||||||
|
export type Session = typeof Session.Type;
|
||||||
|
export type User = typeof User.Type;
|
||||||
@@ -20,7 +20,7 @@ export const Sha256 = pipe(
|
|||||||
(array) => array.byteLength === 32,
|
(array) => array.byteLength === 32,
|
||||||
() => B.error(`Expected Uint8Array to be 32 bytes long`),
|
() => B.error(`Expected Uint8Array to be 32 bytes long`),
|
||||||
)),
|
)),
|
||||||
);
|
).annotations({ identifier: "SHA-256" });
|
||||||
|
|
||||||
export class BooleanFromNumber extends S.transform(
|
export class BooleanFromNumber extends S.transform(
|
||||||
S.Number,
|
S.Number,
|
||||||
@@ -31,67 +31,3 @@ export class BooleanFromNumber extends S.transform(
|
|||||||
encode: (i) => i ? 1 : 0,
|
encode: (i) => i ? 1 : 0,
|
||||||
},
|
},
|
||||||
).annotations({ identifier: "BooleanFromNumber" }) { }
|
).annotations({ identifier: "BooleanFromNumber" }) { }
|
||||||
|
|
||||||
export const SystemInformation = S.Struct({
|
|
||||||
createdBy: S.Union(UserId, S.Null),
|
|
||||||
createdAt: S.DateTimeUtc,
|
|
||||||
modifiedBy: S.Union(UserId, S.Null),
|
|
||||||
modifiedAt: S.DateTimeUtc,
|
|
||||||
});
|
|
||||||
|
|
||||||
export type SystemInformation = typeof SystemInformation.Type;
|
|
||||||
|
|
||||||
// --- TABLES ------------------------------------------------------------------
|
|
||||||
|
|
||||||
export const AccessLog = S.Struct({
|
|
||||||
timestamp: S.DateTimeUtc,
|
|
||||||
requestId: RequestId,
|
|
||||||
method: S.NonEmptyString,
|
|
||||||
pathname: S.NonEmptyString,
|
|
||||||
query: S.parseJson(S.Record({
|
|
||||||
key: S.String,
|
|
||||||
value: S.String,
|
|
||||||
})),
|
|
||||||
ip: S.Union(S.NonEmptyString, S.Null),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const Attachment = pipe(
|
|
||||||
S.Struct({
|
|
||||||
attachmentId: AttachmentId,
|
|
||||||
pieceId: PieceId,
|
|
||||||
sha256: Sha256,
|
|
||||||
filename: S.NonEmptyString,
|
|
||||||
mediaType: S.NonEmptyString,
|
|
||||||
}),
|
|
||||||
S.extend(SystemInformation),
|
|
||||||
);
|
|
||||||
|
|
||||||
export const Piece = pipe(
|
|
||||||
S.Struct({
|
|
||||||
pieceId: PieceId,
|
|
||||||
name: S.NonEmptyString,
|
|
||||||
composer: S.Union(S.NonEmptyString, S.Null),
|
|
||||||
lyricist: S.Union(S.NonEmptyString, S.Null),
|
|
||||||
arranger: S.Union(S.NonEmptyString, S.Null),
|
|
||||||
}),
|
|
||||||
S.extend(SystemInformation),
|
|
||||||
);
|
|
||||||
|
|
||||||
export const Session = S.Struct({
|
|
||||||
sessionId: SessionId,
|
|
||||||
userId: UserId,
|
|
||||||
expiresAt: S.DateTimeUtc,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const User = S.Struct({
|
|
||||||
userId: UserId,
|
|
||||||
username: S.NonEmptyString,
|
|
||||||
password: S.NonEmptyString,
|
|
||||||
admin: BooleanFromNumber,
|
|
||||||
});
|
|
||||||
|
|
||||||
export type AccessLog = typeof AccessLog.Type;
|
|
||||||
export type Attachment = typeof Attachment.Type;
|
|
||||||
export type Piece = typeof Piece.Type;
|
|
||||||
export type Session = typeof Session.Type;
|
|
||||||
export type User = typeof User.Type;
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"@effect/schema": "catalog:",
|
"@effect/schema": "catalog:",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
"fast-check": "catalog:",
|
"fast-check": "catalog:",
|
||||||
"preact": "catalog:"
|
"preact": "catalog:",
|
||||||
|
"preact-iso": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
15
packages/frontend/src/App.tsx
Normal file
15
packages/frontend/src/App.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { ErrorBoundary, lazy, LocationProvider, Route, Router } from "preact-iso";
|
||||||
|
|
||||||
|
const Home = lazy(() => import("./routes/Home"));
|
||||||
|
const Login = lazy(() => import("./routes/Login"));
|
||||||
|
|
||||||
|
export const App = () => (
|
||||||
|
<LocationProvider>
|
||||||
|
<ErrorBoundary>
|
||||||
|
<Router>
|
||||||
|
<Route path="/" component={Home} />
|
||||||
|
<Route path="/login" component={Login} />
|
||||||
|
</Router>
|
||||||
|
</ErrorBoundary>
|
||||||
|
</LocationProvider>
|
||||||
|
);
|
||||||
@@ -4,6 +4,10 @@ globalStyle("html, body", {
|
|||||||
margin: 0,
|
margin: 0,
|
||||||
padding: 0,
|
padding: 0,
|
||||||
|
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
overflow: "hidden",
|
||||||
|
|
||||||
backgroundColor: "white",
|
backgroundColor: "white",
|
||||||
color: "black",
|
color: "black",
|
||||||
|
|
||||||
@@ -13,7 +17,26 @@ globalStyle("html, body", {
|
|||||||
|
|
||||||
"@media": {
|
"@media": {
|
||||||
"(prefers-color-scheme: dark)": {
|
"(prefers-color-scheme: dark)": {
|
||||||
backgroundColor: "black",
|
backgroundColor: "#1E1E1E",
|
||||||
|
color: "white",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
globalStyle("input, button, select, textarea", {
|
||||||
|
border: "none",
|
||||||
|
outline: "none",
|
||||||
|
backgroundImage: "none",
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
|
||||||
|
fontFamily: "system-ui, sans-serif",
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: "normal",
|
||||||
|
|
||||||
|
color: "black",
|
||||||
|
|
||||||
|
"@media": {
|
||||||
|
"(prefers-color-scheme: dark)": {
|
||||||
color: "white",
|
color: "white",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { render } from "preact";
|
import { render } from "preact";
|
||||||
|
import { App } from "./App";
|
||||||
|
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
render(<p>Hello World</p>, document.body);
|
render(<App />, document.body);
|
||||||
|
|||||||
1
packages/frontend/src/routes/Home.tsx
Normal file
1
packages/frontend/src/routes/Home.tsx
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default () => null;
|
||||||
138
packages/frontend/src/routes/Login.css.ts
Normal file
138
packages/frontend/src/routes/Login.css.ts
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
import { style } from "@vanilla-extract/css";
|
||||||
|
|
||||||
|
export const container = style({
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const box = style({
|
||||||
|
padding: 8,
|
||||||
|
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: 8,
|
||||||
|
|
||||||
|
borderWidth: 1,
|
||||||
|
borderStyle: "solid",
|
||||||
|
borderColor: "black",
|
||||||
|
|
||||||
|
borderRadius: 4,
|
||||||
|
|
||||||
|
"@media": {
|
||||||
|
"(prefers-color-scheme: dark)": {
|
||||||
|
borderColor: "white",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const header = style({
|
||||||
|
paddingBottom: 8,
|
||||||
|
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomStyle: "solid",
|
||||||
|
borderBottomColor: "black",
|
||||||
|
|
||||||
|
textAlign: "center",
|
||||||
|
fontWeight: "bold",
|
||||||
|
|
||||||
|
"@media": {
|
||||||
|
"(prefers-color-scheme: dark)": {
|
||||||
|
borderBottomColor: "white",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const input = style({
|
||||||
|
width: "32ch",
|
||||||
|
padding: 8,
|
||||||
|
|
||||||
|
borderWidth: 1,
|
||||||
|
borderStyle: "solid",
|
||||||
|
borderColor: "black",
|
||||||
|
|
||||||
|
borderRadius: 4,
|
||||||
|
|
||||||
|
"selectors": {
|
||||||
|
"&:focus": {
|
||||||
|
outlineWidth: 2,
|
||||||
|
outlineStyle: "solid",
|
||||||
|
outlineColor: "#8080FF",
|
||||||
|
|
||||||
|
"@media": {
|
||||||
|
"(prefers-color-scheme: dark)": {
|
||||||
|
outlineColor: "#C0C0FF",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"@media": {
|
||||||
|
"(prefers-color-scheme: dark)": {
|
||||||
|
borderColor: "white",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const submit = style({
|
||||||
|
padding: 8,
|
||||||
|
|
||||||
|
backgroundColor: "#C0C0C0",
|
||||||
|
|
||||||
|
borderWidth: 2,
|
||||||
|
borderStyle: "solid",
|
||||||
|
borderTopColor: "#E0E0E0",
|
||||||
|
borderLeftColor: "#E0E0E0",
|
||||||
|
borderRightColor: "#404040",
|
||||||
|
borderBottomColor: "#404040",
|
||||||
|
|
||||||
|
borderRadius: 4,
|
||||||
|
|
||||||
|
cursor: "pointer",
|
||||||
|
|
||||||
|
"selectors": {
|
||||||
|
"&:focus": {
|
||||||
|
outlineWidth: 2,
|
||||||
|
outlineStyle: "solid",
|
||||||
|
outlineColor: "#8080FF",
|
||||||
|
|
||||||
|
"@media": {
|
||||||
|
"(prefers-color-scheme: dark)": {
|
||||||
|
outlineColor: "#C0C0FF",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"&:active": {
|
||||||
|
borderTopColor: "#404040",
|
||||||
|
borderLeftColor: "#404040",
|
||||||
|
borderRightColor: "#E0E0E0",
|
||||||
|
borderBottomColor: "#E0E0E0",
|
||||||
|
|
||||||
|
"@media": {
|
||||||
|
"(prefers-color-scheme: dark)": {
|
||||||
|
outlineColor: "#C0C0FF",
|
||||||
|
|
||||||
|
borderTopColor: "#202020",
|
||||||
|
borderLeftColor: "#202020",
|
||||||
|
borderRightColor: "#606060",
|
||||||
|
borderBottomColor: "#606060",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"@media": {
|
||||||
|
"(prefers-color-scheme: dark)": {
|
||||||
|
backgroundColor: "#404040",
|
||||||
|
|
||||||
|
borderTopColor: "#606060",
|
||||||
|
borderLeftColor: "#606060",
|
||||||
|
borderRightColor: "#202020",
|
||||||
|
borderBottomColor: "#202020",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
92
packages/frontend/src/routes/Login.tsx
Normal file
92
packages/frontend/src/routes/Login.tsx
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import { Schema as S } from "@effect/schema";
|
||||||
|
import { LoginRequest, LoginResponse } from "common/api";
|
||||||
|
import { useId, useMemo, useRef } from "preact/hooks";
|
||||||
|
import { useStore } from "../store";
|
||||||
|
import * as style from "./Login.css";
|
||||||
|
import { Effect, Fiber, pipe } from "effect";
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
|
||||||
|
const loginUsername = useStore(state => state.loginUsername);
|
||||||
|
const loginPassword = useStore(state => state.loginPassword);
|
||||||
|
|
||||||
|
const setLoginUsername = useStore(state => state.setLoginUsername);
|
||||||
|
const setLoginPassword = useStore(state => state.setLoginPassword);
|
||||||
|
|
||||||
|
const usernameId = useId();
|
||||||
|
const passwordId = useId();
|
||||||
|
|
||||||
|
const requestFiber = useRef<Fiber.RuntimeFiber<void> | null>(null);
|
||||||
|
const requestEffect = useMemo(() => Effect.gen(function* () {
|
||||||
|
const requestData = LoginRequest.make(LoginRequest.make({ username: loginUsername, password: loginPassword }));
|
||||||
|
|
||||||
|
const requestJson = yield* pipe(
|
||||||
|
requestData,
|
||||||
|
S.encode(LoginRequest),
|
||||||
|
Effect.map(JSON.stringify),
|
||||||
|
Effect.orDie,
|
||||||
|
);
|
||||||
|
|
||||||
|
const res = yield* Effect.promise((signal) => fetch("/api/login", {
|
||||||
|
method: "POST",
|
||||||
|
body: requestJson,
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
signal,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
yield* Effect.die(new Error("Response was not ok"));
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseData = yield* pipe(
|
||||||
|
Effect.promise(() => res.json()),
|
||||||
|
Effect.flatMap(S.decodeUnknown(LoginResponse)),
|
||||||
|
Effect.orDie,
|
||||||
|
);
|
||||||
|
|
||||||
|
setLoginUsername("");
|
||||||
|
setLoginPassword("");
|
||||||
|
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.href = "/";
|
||||||
|
a.click();
|
||||||
|
}), [loginUsername, loginPassword]);
|
||||||
|
|
||||||
|
const onSubmit = (e: SubmitEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (requestFiber.current !== null) {
|
||||||
|
Effect.runFork(Fiber.interrupt(requestFiber.current), { immediate: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
requestFiber.current = Effect.runFork(requestEffect);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class={style.container}>
|
||||||
|
<form class={style.box} onSubmit={onSubmit}>
|
||||||
|
<header class={style.header}>Repozytorium muzyczne</header>
|
||||||
|
<label for={usernameId}>Nazwa użytkownika</label>
|
||||||
|
<input
|
||||||
|
id={usernameId}
|
||||||
|
class={style.input}
|
||||||
|
type="text"
|
||||||
|
value={loginUsername}
|
||||||
|
autofocus
|
||||||
|
required
|
||||||
|
onInput={(e) => setLoginUsername(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<label for={passwordId}>Hasło</label>
|
||||||
|
<input
|
||||||
|
id={passwordId}
|
||||||
|
class={style.input}
|
||||||
|
type="password"
|
||||||
|
value={loginPassword}
|
||||||
|
required
|
||||||
|
onInput={(e) => setLoginPassword(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<button class={style.submit} type="submit">Zaloguj się</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -5,11 +5,19 @@ export type Update<T> = T | ((prev: T) => T);
|
|||||||
export type Updater<T> = (action: Update<T>) => void;
|
export type Updater<T> = (action: Update<T>) => void;
|
||||||
|
|
||||||
export interface Store {
|
export interface Store {
|
||||||
readonly count: number;
|
readonly loginUsername: string;
|
||||||
|
readonly loginPassword: string;
|
||||||
|
|
||||||
|
readonly setLoginUsername: Updater<string>;
|
||||||
|
readonly setLoginPassword: Updater<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
let store: Store = Object.freeze<Store>({
|
let store: Store = Object.freeze<Store>({
|
||||||
count: 0,
|
loginUsername: "",
|
||||||
|
loginPassword: "",
|
||||||
|
|
||||||
|
setLoginUsername: (action) => set(({ loginUsername }) => ({ loginUsername: typeof action === "function" ? action(loginUsername) : loginUsername })),
|
||||||
|
setLoginPassword: (action) => set(({ loginPassword }) => ({ loginPassword: typeof action === "function" ? action(loginPassword) : loginPassword })),
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- STORE IMPLEMENTATION ----------------------------------------------------
|
// --- STORE IMPLEMENTATION ----------------------------------------------------
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../tsconfig.base.json",
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"references": [
|
||||||
|
{ "path": "../common" },
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
26
pnpm-lock.yaml
generated
26
pnpm-lock.yaml
generated
@@ -24,6 +24,9 @@ catalogs:
|
|||||||
preact:
|
preact:
|
||||||
specifier: ^10.23.1
|
specifier: ^10.23.1
|
||||||
version: 10.23.1
|
version: 10.23.1
|
||||||
|
preact-iso:
|
||||||
|
specifier: ^2.6.3
|
||||||
|
version: 2.6.3
|
||||||
typescript:
|
typescript:
|
||||||
specifier: ^5.6.0-beta
|
specifier: ^5.6.0-beta
|
||||||
version: 5.6.0-dev.20240802
|
version: 5.6.0-dev.20240802
|
||||||
@@ -94,6 +97,9 @@ importers:
|
|||||||
preact:
|
preact:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 10.23.1
|
version: 10.23.1
|
||||||
|
preact-iso:
|
||||||
|
specifier: 'catalog:'
|
||||||
|
version: 2.6.3(preact-render-to-string@6.5.7(preact@10.23.1))(preact@10.23.1)
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@vanilla-extract/css':
|
'@vanilla-extract/css':
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
@@ -664,6 +670,17 @@ packages:
|
|||||||
resolution: {integrity: sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==}
|
resolution: {integrity: sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==}
|
||||||
engines: {node: ^10 || ^12 || >=14}
|
engines: {node: ^10 || ^12 || >=14}
|
||||||
|
|
||||||
|
preact-iso@2.6.3:
|
||||||
|
resolution: {integrity: sha512-2JqNi+Elyt7rf0kULtMn/QskbZE10cDMrhmoanGKWJX3gOCTIfLt/ta5xWu+kQUsyInPkTQIVp3fS5Im0V+X8A==}
|
||||||
|
peerDependencies:
|
||||||
|
preact: '>=10'
|
||||||
|
preact-render-to-string: '>=6.4.0'
|
||||||
|
|
||||||
|
preact-render-to-string@6.5.7:
|
||||||
|
resolution: {integrity: sha512-nACZDdv/ZZciuldVYMcfGqr61DKJeaAfPx96hn6OXoBGhgtU2yGQkA0EpTzWH4SvnwF0syLsL4WK7AIp3Ruc1g==}
|
||||||
|
peerDependencies:
|
||||||
|
preact: '>=10'
|
||||||
|
|
||||||
preact@10.23.1:
|
preact@10.23.1:
|
||||||
resolution: {integrity: sha512-O5UdRsNh4vdZaTieWe3XOgSpdMAmkIYBCT3VhQDlKrzyCm8lUYsk0fmVEvoQQifoOjFRTaHZO69ylrzTW2BH+A==}
|
resolution: {integrity: sha512-O5UdRsNh4vdZaTieWe3XOgSpdMAmkIYBCT3VhQDlKrzyCm8lUYsk0fmVEvoQQifoOjFRTaHZO69ylrzTW2BH+A==}
|
||||||
|
|
||||||
@@ -1279,6 +1296,15 @@ snapshots:
|
|||||||
picocolors: 1.0.1
|
picocolors: 1.0.1
|
||||||
source-map-js: 1.2.0
|
source-map-js: 1.2.0
|
||||||
|
|
||||||
|
preact-iso@2.6.3(preact-render-to-string@6.5.7(preact@10.23.1))(preact@10.23.1):
|
||||||
|
dependencies:
|
||||||
|
preact: 10.23.1
|
||||||
|
preact-render-to-string: 6.5.7(preact@10.23.1)
|
||||||
|
|
||||||
|
preact-render-to-string@6.5.7(preact@10.23.1):
|
||||||
|
dependencies:
|
||||||
|
preact: 10.23.1
|
||||||
|
|
||||||
preact@10.23.1: {}
|
preact@10.23.1: {}
|
||||||
|
|
||||||
pure-rand@6.1.0: {}
|
pure-rand@6.1.0: {}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ catalog:
|
|||||||
effect: '^3.6.0'
|
effect: '^3.6.0'
|
||||||
fast-check: '^3.20.0'
|
fast-check: '^3.20.0'
|
||||||
preact: '^10.23.1'
|
preact: '^10.23.1'
|
||||||
|
preact-iso: '^2.6.3'
|
||||||
typescript: '^5.6.0-beta'
|
typescript: '^5.6.0-beta'
|
||||||
ulid: '^2.3.0'
|
ulid: '^2.3.0'
|
||||||
vite: '^5.3.5'
|
vite: '^5.3.5'
|
||||||
|
|||||||
@@ -28,6 +28,8 @@
|
|||||||
|
|
||||||
"paths": {
|
"paths": {
|
||||||
"common": ["./packages/common/src/index.ts"],
|
"common": ["./packages/common/src/index.ts"],
|
||||||
|
"common/api": ["./packages/common/src/api.ts"],
|
||||||
|
"common/db": ["./packages/common/src/db.ts"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"include": ["${configDir}/src"],
|
"include": ["${configDir}/src"],
|
||||||
|
|||||||
Reference in New Issue
Block a user