Big restructure
This commit is contained in:
parent
1d0ebeb2b7
commit
4ee57ab459
69 changed files with 124 additions and 124 deletions
20
apps/web/src/components/modals/basic.tsx
Normal file
20
apps/web/src/components/modals/basic.tsx
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import type { User } from "@tensamin/user/context";
|
||||
import Avatar from "@tensamin/ui/avatar";
|
||||
import { reduceDisplay } from "./utils";
|
||||
import { Card, CardHeader } from "@tensamin/ui/card";
|
||||
|
||||
export default function Basic(props: { user: User }) {
|
||||
return (
|
||||
<Card className="animate-in fade-in duration-300 rounded-2xl">
|
||||
<CardHeader className="flex flex-row gap-2.5 items-center justify-start p-2">
|
||||
<Avatar
|
||||
img={props.user.avatar}
|
||||
fallback={reduceDisplay(props.user.display)}
|
||||
/>
|
||||
<div className="flex flex-col gap-1 w-full items-start justify-center text-[15px]">
|
||||
<p>{props.user.display}</p>
|
||||
</div>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
8
apps/web/src/components/modals/utils.ts
Normal file
8
apps/web/src/components/modals/utils.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
export function reduceDisplay(display: string) {
|
||||
const words = display.split(" ");
|
||||
if (words.length === 1) {
|
||||
return display.slice(0, 2).toUpperCase();
|
||||
} else {
|
||||
return words[0].charAt(0).toUpperCase() + words[1].charAt(0).toUpperCase();
|
||||
}
|
||||
}
|
||||
45
apps/web/src/components/navbar.tsx
Normal file
45
apps/web/src/components/navbar.tsx
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import { Button } from "@tensamin/ui/button";
|
||||
import { House } from "lucide-react";
|
||||
import { useNavigate, useRouterState } from "@tanstack/react-router";
|
||||
import * as React from "react";
|
||||
import { useUser, type User } from "@tensamin/user/context";
|
||||
|
||||
export default function Navbar() {
|
||||
const navigate = useNavigate();
|
||||
const { get } = useUser();
|
||||
const search = useRouterState({ select: (state) => state.location.search });
|
||||
|
||||
const [user, setUser] = React.useState<User | null>(null);
|
||||
|
||||
const currentId = React.useMemo(
|
||||
() => Number((search as Record<string, unknown>).id ?? Number.NaN),
|
||||
[search],
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (Number.isNaN(currentId)) {
|
||||
setUser(null);
|
||||
return;
|
||||
}
|
||||
|
||||
get(currentId)
|
||||
.then(setUser)
|
||||
.catch(() => setUser(null));
|
||||
}, [currentId, get]);
|
||||
|
||||
return (
|
||||
<div className="w-full h-13.5 flex items-center justify-center">
|
||||
<Button
|
||||
onClick={() => {
|
||||
void navigate({ to: "/" });
|
||||
}}
|
||||
className="w-9 h-9 aspect-square p-0 rounded-lg"
|
||||
variant="outline"
|
||||
>
|
||||
<House size={18} />
|
||||
</Button>
|
||||
<p className="font-medium pl-3 text-md">{user?.display}</p>
|
||||
<div className="w-full" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
138
apps/web/src/components/screens/login/form.tsx
Normal file
138
apps/web/src/components/screens/login/form.tsx
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
import { Button } from "@tensamin/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@tensamin/ui/card";
|
||||
import { Input } from "@tensamin/ui/input";
|
||||
import { Label } from "@tensamin/ui/label";
|
||||
import { useStorage } from "@tensamin/storage/context";
|
||||
import { log, toast } from "@tensamin/shared/log";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
import { Upload } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { z } from "zod";
|
||||
|
||||
const fetchedUser = z.object({
|
||||
id: z.uuidv4(),
|
||||
type: z.string(),
|
||||
data: z.object({
|
||||
iota_id: z.number(),
|
||||
username: z.string(),
|
||||
sub_level: z.number(),
|
||||
public_key: z.base64(),
|
||||
user_id: z.number(),
|
||||
sub_end: z.number(),
|
||||
}),
|
||||
});
|
||||
|
||||
const formSchema = z.object({
|
||||
username: z.string().min(1).max(15),
|
||||
private_key: z.string().min(1).max(92),
|
||||
});
|
||||
|
||||
export default function Form() {
|
||||
const uploadRef = React.useRef<HTMLInputElement | null>(null);
|
||||
const { save } = useStorage();
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<div className="flex gap-5">
|
||||
<Card className="w-75 h-80">
|
||||
<CardHeader>
|
||||
<CardTitle>Use .tu file</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="h-full flex items-center justify-center">
|
||||
<div
|
||||
onClick={() => uploadRef.current?.click()}
|
||||
className="cursor-pointer w-60 aspect-square mb-17 bg-input/13 hover:bg-input/30 transition-all duration-300 ease-in-out border-dotted border-input/75 border-3 flex items-center justify-center rounded-lg"
|
||||
>
|
||||
<Upload className="text-input/75" size={34} />
|
||||
</div>
|
||||
<input
|
||||
onChange={async (e) => {
|
||||
try {
|
||||
const file = e.currentTarget.files?.[0];
|
||||
if (file) {
|
||||
const raw = await file.text();
|
||||
if (raw.length !== 92) throw new Error("Invalid file");
|
||||
if (!raw.includes("::")) throw new Error("Invalid file");
|
||||
const [userIdString, privateKey] = raw.split("::");
|
||||
const userId = Number(userIdString);
|
||||
if (!userId || !privateKey) throw new Error("Invalid file");
|
||||
|
||||
save("user_id", userId);
|
||||
save("private_key", privateKey);
|
||||
|
||||
void navigate({ to: "/" });
|
||||
} else {
|
||||
throw new Error("No file selected");
|
||||
}
|
||||
} catch (err) {
|
||||
log(0, "Login", "red", err);
|
||||
toast("error", "Failed to load file");
|
||||
}
|
||||
}}
|
||||
type="file"
|
||||
ref={uploadRef}
|
||||
className="hidden"
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="w-75 h-auto">
|
||||
<CardHeader>
|
||||
<CardTitle>Use credentials</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form
|
||||
className="flex flex-col gap-5 h-full"
|
||||
onSubmit={async (e) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.currentTarget);
|
||||
const rawData = Object.fromEntries(formData);
|
||||
const inputParse = formSchema.safeParse(rawData);
|
||||
|
||||
if (!inputParse.success) {
|
||||
toast("error", "Please enter valid data");
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
"https://omega.tensamin.net/api/get/id/" +
|
||||
inputParse.data.username,
|
||||
);
|
||||
|
||||
response
|
||||
.json()
|
||||
.then((data) => {
|
||||
const parse = fetchedUser.safeParse(data);
|
||||
|
||||
if (parse.success) {
|
||||
const user = parse.data;
|
||||
|
||||
save("user_id", user.data.user_id);
|
||||
save("private_key", inputParse.data.private_key);
|
||||
|
||||
void navigate({ to: "/" });
|
||||
} else {
|
||||
log(0, "Login", "red", "Invalid response from server");
|
||||
toast("error", "Invalid response from server");
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
log(0, "Login", "red", err);
|
||||
toast("error", "Failed to fetch user data");
|
||||
});
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label htmlFor="username">Username</Label>
|
||||
<Input type="text" id="username" name="username" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label htmlFor="private_key">Private Key</Label>
|
||||
<Input type="password" id="private_key" name="private_key" />
|
||||
</div>
|
||||
<Button type="submit">Login</Button>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
28
apps/web/src/components/sidebar.tsx
Normal file
28
apps/web/src/components/sidebar.tsx
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { useStorage } from "@tensamin/storage/context";
|
||||
import Wrapper from "@tensamin/user/wrapper";
|
||||
import * as React from "react";
|
||||
import Basic from "./modals/basic";
|
||||
import List from "@/features/conversation/list/body";
|
||||
|
||||
export default function Sidebar() {
|
||||
const [userId, setUserId] = React.useState(0);
|
||||
const { load } = useStorage();
|
||||
|
||||
React.useEffect(() => {
|
||||
load("user_id").then(setUserId);
|
||||
}, [load]);
|
||||
|
||||
return (
|
||||
<div className="w-75 h-full flex flex-col gap-3 p-2">
|
||||
{userId !== 0 && (
|
||||
<Wrapper
|
||||
userId={userId}
|
||||
component={(user) => <Basic user={user} />}
|
||||
/>
|
||||
)}
|
||||
<div className="h-full">
|
||||
<List />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue