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": {
|
"/item": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query: {
|
query: {
|
||||||
@@ -11,12 +11,56 @@ interface paths {
|
|||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
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;
|
return?: never;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Item {
|
export interface Item {
|
||||||
id: number;
|
|
||||||
uuid: string;
|
uuid: string;
|
||||||
name: string;
|
name: string;
|
||||||
rarity: number;
|
rarity: number;
|
||||||
@@ -24,6 +68,18 @@ export interface Item {
|
|||||||
image: string;
|
image: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Case {
|
||||||
|
uuid: string;
|
||||||
|
name: string;
|
||||||
|
price: number;
|
||||||
|
image: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface User {
|
||||||
|
uuid: string;
|
||||||
|
username: string;
|
||||||
|
}
|
||||||
|
|
||||||
class Client {
|
class Client {
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
|
|
||||||
@@ -31,20 +87,44 @@ class Client {
|
|||||||
this.baseUrl = baseUrl;
|
this.baseUrl = baseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
GET<T extends keyof paths>(
|
async GET<T extends keyof get>(
|
||||||
path: T,
|
path: T,
|
||||||
parameters: paths[T]["parameters"]
|
parameters: get[T]["parameters"]
|
||||||
): Promise<paths[T]["return"]> {
|
): Promise<[get[T]["return"], number]> {
|
||||||
let query = parameters["query"]
|
let query = parameters["query"]
|
||||||
? "?" + new URLSearchParams(parameters["query"])
|
? "?" + new URLSearchParams(parameters["query"])
|
||||||
: "";
|
: "";
|
||||||
return fetch(this.baseUrl + path + query, {
|
let response = await fetch(this.baseUrl + path + query, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"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 [
|
export default [
|
||||||
index("routes/home.tsx"),
|
index("routes/home.tsx"),
|
||||||
route("/test", "routes/test.tsx"),
|
route("/login", "routes/login.tsx"),
|
||||||
|
route("/register", "routes/register.tsx"),
|
||||||
] satisfies RouteConfig;
|
] 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",
|
"motion": "^12.5.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-router": "^7.3.0"
|
"react-router": "^7.3.0",
|
||||||
|
"zustand": "^5.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-router/dev": "^7.3.0",
|
"@react-router/dev": "^7.3.0",
|
||||||
|
|||||||
26
pnpm-lock.yaml
generated
26
pnpm-lock.yaml
generated
@@ -29,6 +29,9 @@ importers:
|
|||||||
react-router:
|
react-router:
|
||||||
specifier: ^7.3.0
|
specifier: ^7.3.0
|
||||||
version: 7.3.0(react-dom@19.0.0(react@19.0.0))(react@19.0.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:
|
devDependencies:
|
||||||
'@react-router/dev':
|
'@react-router/dev':
|
||||||
specifier: ^7.3.0
|
specifier: ^7.3.0
|
||||||
@@ -2296,6 +2299,24 @@ packages:
|
|||||||
resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
|
resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
|
||||||
engines: {node: '>= 6'}
|
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:
|
snapshots:
|
||||||
|
|
||||||
'@ampproject/remapping@2.3.0':
|
'@ampproject/remapping@2.3.0':
|
||||||
@@ -4474,3 +4495,8 @@ snapshots:
|
|||||||
yallist@3.1.1: {}
|
yallist@3.1.1: {}
|
||||||
|
|
||||||
yaml@1.10.2: {}
|
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