Compare commits
7 Commits
aee597acf2
...
ec9d9ad1c5
| Author | SHA1 | Date | |
|---|---|---|---|
| ec9d9ad1c5 | |||
| 983a63ce0b | |||
| 46625b03a8 | |||
| 83eb5656c1 | |||
| ca7fd3edfa | |||
| 5699a4a60b | |||
| 8ee097b774 |
@@ -1,4 +1,4 @@
|
||||
interface paths {
|
||||
interface get {
|
||||
"/item": {
|
||||
parameters: {
|
||||
query: {
|
||||
@@ -11,12 +11,56 @@ interface paths {
|
||||
parameters: {
|
||||
query?: never;
|
||||
};
|
||||
return?: Array<Item>;
|
||||
};
|
||||
"/case": {
|
||||
parameters: {
|
||||
query: {
|
||||
uuid: string;
|
||||
};
|
||||
};
|
||||
return: Case;
|
||||
};
|
||||
"/cases": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
};
|
||||
return?: Array<Case>;
|
||||
};
|
||||
}
|
||||
|
||||
interface post {
|
||||
"/login": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
body: {
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
};
|
||||
return: User;
|
||||
};
|
||||
"/logout": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
body?: never;
|
||||
};
|
||||
return?: never;
|
||||
};
|
||||
"/register": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
body: {
|
||||
username: string;
|
||||
password: string;
|
||||
email: string;
|
||||
};
|
||||
};
|
||||
return?: never;
|
||||
};
|
||||
}
|
||||
|
||||
export interface Item {
|
||||
id: number;
|
||||
uuid: string;
|
||||
name: string;
|
||||
rarity: number;
|
||||
@@ -24,6 +68,18 @@ export interface Item {
|
||||
image: string;
|
||||
}
|
||||
|
||||
export interface Case {
|
||||
uuid: string;
|
||||
name: string;
|
||||
price: number;
|
||||
image: string;
|
||||
}
|
||||
|
||||
export interface User {
|
||||
uuid: string;
|
||||
username: string;
|
||||
}
|
||||
|
||||
class Client {
|
||||
baseUrl: string;
|
||||
|
||||
@@ -31,20 +87,44 @@ class Client {
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
GET<T extends keyof paths>(
|
||||
async GET<T extends keyof get>(
|
||||
path: T,
|
||||
parameters: paths[T]["parameters"]
|
||||
): Promise<paths[T]["return"]> {
|
||||
parameters: get[T]["parameters"]
|
||||
): Promise<[get[T]["return"], number]> {
|
||||
let query = parameters["query"]
|
||||
? "?" + new URLSearchParams(parameters["query"])
|
||||
: "";
|
||||
return fetch(this.baseUrl + path + query, {
|
||||
let response = await fetch(this.baseUrl + path + query, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
// body: parameters ? JSON.stringify(parameters) : undefined,
|
||||
}).then((response) => response.json());
|
||||
});
|
||||
let data = await response.json();
|
||||
return [data, response.status];
|
||||
}
|
||||
|
||||
async POST<T extends keyof post>(
|
||||
path: T,
|
||||
parameters: post[T]["parameters"]
|
||||
): Promise<[post[T]["return"], number]> {
|
||||
let query = parameters["query"]
|
||||
? "?" + new URLSearchParams(parameters["query"])
|
||||
: "";
|
||||
let response = await fetch(this.baseUrl + path + query, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(parameters["body"]),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
let data;
|
||||
try {
|
||||
data = await response.json();
|
||||
} catch (_) {
|
||||
data = null;
|
||||
}
|
||||
return [data, response.status];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
20
app/hooks/user.tsx
Normal file
20
app/hooks/user.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { create } from "zustand";
|
||||
import { persist } from "zustand/middleware";
|
||||
import type { User } from "../api/client";
|
||||
|
||||
interface UserStore {
|
||||
user: User | null;
|
||||
setUser: (user: User | null) => void;
|
||||
}
|
||||
|
||||
export const useUserStore = create<UserStore>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
user: null,
|
||||
setUser: (user: User | null) => set({ user }),
|
||||
}),
|
||||
{
|
||||
name: "user",
|
||||
}
|
||||
)
|
||||
);
|
||||
59
app/pages/login.tsx
Normal file
59
app/pages/login.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import { useState } from "react";
|
||||
import { useNavigate } from "react-router";
|
||||
import client from "~/api/client";
|
||||
import { useUserStore } from "~/hooks/user";
|
||||
|
||||
export default function LoginPage() {
|
||||
const user = useUserStore((state) => state.user);
|
||||
const setUser = useUserStore((state) => state.setUser);
|
||||
const [error, setError] = useState(false);
|
||||
const navigator = useNavigate();
|
||||
|
||||
async function login(event: React.FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
const formData = new FormData(event.currentTarget);
|
||||
let username = formData.get("username");
|
||||
let password = formData.get("password");
|
||||
let [newUser, status] = await client.POST("/login", {
|
||||
body: {
|
||||
username: username as string,
|
||||
password: password as string,
|
||||
},
|
||||
});
|
||||
if (status === 200) {
|
||||
console.log(newUser);
|
||||
setUser(newUser);
|
||||
navigator("/");
|
||||
} else {
|
||||
setError(true);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{user !== null ? (
|
||||
<h1>You are already logged in.</h1>
|
||||
) : (
|
||||
<>
|
||||
<h1>Login</h1>
|
||||
{error && (
|
||||
<h2 style={{ color: "red" }}>
|
||||
An error occurred. Please try again.
|
||||
</h2>
|
||||
)}
|
||||
<form onSubmit={login}>
|
||||
<label>
|
||||
Username:
|
||||
<input type="text" name="username" />
|
||||
</label>
|
||||
<label>
|
||||
Password:
|
||||
<input type="password" name="password" />
|
||||
</label>
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
54
app/pages/register.tsx
Normal file
54
app/pages/register.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import { useState } from "react";
|
||||
import { useNavigate } from "react-router";
|
||||
import client from "~/api/client";
|
||||
import { useUserStore } from "~/hooks/user";
|
||||
|
||||
export default function RegisterPage() {
|
||||
const user = useUserStore((state) => state.user);
|
||||
const [error, setError] = useState(false);
|
||||
const navigator = useNavigate();
|
||||
|
||||
async function login(formData: FormData) {
|
||||
let username = formData.get("username");
|
||||
let password = formData.get("password");
|
||||
let email = formData.get("email");
|
||||
let [_, status] = await client.POST("/register", {
|
||||
body: {
|
||||
username: username as string,
|
||||
password: password as string,
|
||||
email: email as string,
|
||||
},
|
||||
});
|
||||
if (status === 200) {
|
||||
navigator("/");
|
||||
} else {
|
||||
setError(true);
|
||||
}
|
||||
}
|
||||
|
||||
return user ? (
|
||||
<h1>You are already logged in.</h1>
|
||||
) : (
|
||||
<div>
|
||||
<h1>Register</h1>
|
||||
{error && (
|
||||
<h2 style={{ color: "red" }}>An error occurred. Please try again.</h2>
|
||||
)}
|
||||
<form action={login}>
|
||||
<label>
|
||||
Username:
|
||||
<input type="text" name="username" />
|
||||
</label>
|
||||
<label>
|
||||
Password:
|
||||
<input type="password" name="password" />
|
||||
</label>
|
||||
<label>
|
||||
Email:
|
||||
<input type="email" name="email" />
|
||||
</label>
|
||||
<button type="submit">Register</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import type { Item } from "~/api/client";
|
||||
|
||||
export default function Test({ item }: { item: Item }) {
|
||||
return <div>{item.name}</div>;
|
||||
}
|
||||
@@ -2,5 +2,6 @@ import { type RouteConfig, index, route } from "@react-router/dev/routes";
|
||||
|
||||
export default [
|
||||
index("routes/home.tsx"),
|
||||
route("/test", "routes/test.tsx"),
|
||||
route("/login", "routes/login.tsx"),
|
||||
route("/register", "routes/register.tsx"),
|
||||
] satisfies RouteConfig;
|
||||
|
||||
17
app/routes/login.tsx
Normal file
17
app/routes/login.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { Route } from "./+types/login";
|
||||
import LoginPage from "../pages/login";
|
||||
|
||||
export function meta({}: Route.MetaArgs) {
|
||||
return [
|
||||
{ title: "Aindustries' casino" },
|
||||
{ name: "description", content: "Welcome to React Router!" },
|
||||
];
|
||||
}
|
||||
|
||||
export async function clientLoader() {}
|
||||
|
||||
export function HydrateFallback() {}
|
||||
|
||||
export default function Login() {
|
||||
return <LoginPage />;
|
||||
}
|
||||
17
app/routes/register.tsx
Normal file
17
app/routes/register.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { Route } from "./+types/login";
|
||||
import RegisterPage from "../pages/register";
|
||||
|
||||
export function meta({}: Route.MetaArgs) {
|
||||
return [
|
||||
{ title: "Aindustries' casino" },
|
||||
{ name: "description", content: "Welcome to React Router!" },
|
||||
];
|
||||
}
|
||||
|
||||
export async function clientLoader() {}
|
||||
|
||||
export function HydrateFallback() {}
|
||||
|
||||
export default function Register() {
|
||||
return <RegisterPage />;
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import type { Route } from "./+types/home";
|
||||
import Test from "../pages/test";
|
||||
import client from "~/api/client";
|
||||
import type { Item } from "~/api/client";
|
||||
|
||||
export function meta({}: Route.MetaArgs) {
|
||||
return [
|
||||
{ title: "Aindustries' casino" },
|
||||
{ name: "description", content: "Welcome to React Router!" },
|
||||
];
|
||||
}
|
||||
|
||||
export async function clientLoader() {
|
||||
let item = await client.GET("/item", {
|
||||
query: { uuid: "eee91ea1-1827-482b-b298-63bd6eda0221" },
|
||||
});
|
||||
return item;
|
||||
}
|
||||
|
||||
export default function TestPage({ loaderData: item }: { loaderData: Item }) {
|
||||
return <Test item={item} />;
|
||||
}
|
||||
@@ -15,7 +15,8 @@
|
||||
"motion": "^12.5.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-router": "^7.3.0"
|
||||
"react-router": "^7.3.0",
|
||||
"zustand": "^5.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-router/dev": "^7.3.0",
|
||||
|
||||
26
pnpm-lock.yaml
generated
26
pnpm-lock.yaml
generated
@@ -29,6 +29,9 @@ importers:
|
||||
react-router:
|
||||
specifier: ^7.3.0
|
||||
version: 7.3.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
zustand:
|
||||
specifier: ^5.0.3
|
||||
version: 5.0.3(@types/react@19.0.10)(react@19.0.0)
|
||||
devDependencies:
|
||||
'@react-router/dev':
|
||||
specifier: ^7.3.0
|
||||
@@ -2296,6 +2299,24 @@ packages:
|
||||
resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
zustand@5.0.3:
|
||||
resolution: {integrity: sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==}
|
||||
engines: {node: '>=12.20.0'}
|
||||
peerDependencies:
|
||||
'@types/react': '>=18.0.0'
|
||||
immer: '>=9.0.6'
|
||||
react: '>=18.0.0'
|
||||
use-sync-external-store: '>=1.2.0'
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
immer:
|
||||
optional: true
|
||||
react:
|
||||
optional: true
|
||||
use-sync-external-store:
|
||||
optional: true
|
||||
|
||||
snapshots:
|
||||
|
||||
'@ampproject/remapping@2.3.0':
|
||||
@@ -4474,3 +4495,8 @@ snapshots:
|
||||
yallist@3.1.1: {}
|
||||
|
||||
yaml@1.10.2: {}
|
||||
|
||||
zustand@5.0.3(@types/react@19.0.10)(react@19.0.0):
|
||||
optionalDependencies:
|
||||
'@types/react': 19.0.10
|
||||
react: 19.0.0
|
||||
|
||||
Reference in New Issue
Block a user