forked from Kieran/void.cat
Use logo as default avatar
This commit is contained in:
parent
9837d917b9
commit
097f97edc1
@ -1,192 +1,192 @@
|
|||||||
import {
|
import {
|
||||||
AdminProfile,
|
AdminProfile,
|
||||||
AdminUserListResult,
|
AdminUserListResult,
|
||||||
ApiError,
|
ApiError,
|
||||||
ApiKey,
|
ApiKey,
|
||||||
LoginSession,
|
LoginSession,
|
||||||
PagedRequest,
|
PagedRequest,
|
||||||
PagedResponse,
|
PagedResponse,
|
||||||
PaymentOrder,
|
PaymentOrder,
|
||||||
Profile,
|
Profile,
|
||||||
SetPaymentConfigRequest,
|
SetPaymentConfigRequest,
|
||||||
SiteInfoResponse,
|
SiteInfoResponse,
|
||||||
VoidFileResponse,
|
VoidFileResponse,
|
||||||
} from "./index";
|
} from "./index";
|
||||||
import {
|
import {
|
||||||
ProgressHandler,
|
ProgressHandler,
|
||||||
ProxyChallengeHandler,
|
ProxyChallengeHandler,
|
||||||
StateChangeHandler,
|
StateChangeHandler,
|
||||||
VoidUploader,
|
VoidUploader,
|
||||||
} from "./upload";
|
} from "./upload";
|
||||||
import {StreamUploader} from "./stream-uploader";
|
import { StreamUploader } from "./stream-uploader";
|
||||||
import {XHRUploader} from "./xhr-uploader";
|
import { XHRUploader } from "./xhr-uploader";
|
||||||
|
|
||||||
export type AuthHandler = (url: string, method: string) => Promise<string>;
|
export type AuthHandler = (url: string, method: string) => Promise<string>;
|
||||||
|
|
||||||
export class VoidApi {
|
export class VoidApi {
|
||||||
readonly #uri: string;
|
readonly #uri: string;
|
||||||
readonly #auth?: AuthHandler;
|
readonly #auth?: AuthHandler;
|
||||||
|
|
||||||
constructor(uri?: string, auth?: AuthHandler) {
|
constructor(uri?: string, auth?: AuthHandler) {
|
||||||
this.#uri = uri ?? "";
|
this.#uri = uri ?? "";
|
||||||
this.#auth = auth;
|
this.#auth = auth;
|
||||||
|
}
|
||||||
|
|
||||||
|
async #req<T>(method: string, url: string, body?: object): Promise<T> {
|
||||||
|
const absoluteUrl = `${this.#uri}${url}`;
|
||||||
|
const headers: HeadersInit = {
|
||||||
|
Accept: "application/json",
|
||||||
|
};
|
||||||
|
if (this.#auth) {
|
||||||
|
headers["Authorization"] = await this.#auth(absoluteUrl, method);
|
||||||
|
}
|
||||||
|
if (body) {
|
||||||
|
headers["Content-Type"] = "application/json";
|
||||||
}
|
}
|
||||||
|
|
||||||
async #req<T>(method: string, url: string, body?: object): Promise<T> {
|
const res = await fetch(absoluteUrl, {
|
||||||
const absoluteUrl = `${this.#uri}${url}`;
|
method,
|
||||||
const headers: HeadersInit = {
|
headers,
|
||||||
Accept: "application/json",
|
mode: "cors",
|
||||||
};
|
body: body ? JSON.stringify(body) : undefined,
|
||||||
if (this.#auth) {
|
});
|
||||||
headers["Authorization"] = await this.#auth(absoluteUrl, method);
|
const text = await res.text();
|
||||||
}
|
if (res.ok) {
|
||||||
if (body) {
|
return text ? (JSON.parse(text) as T) : ({} as T);
|
||||||
headers["Content-Type"] = "application/json";
|
} else {
|
||||||
}
|
throw new ApiError(res.status, text);
|
||||||
|
|
||||||
const res = await fetch(absoluteUrl, {
|
|
||||||
method,
|
|
||||||
headers,
|
|
||||||
mode: "cors",
|
|
||||||
body: body ? JSON.stringify(body) : undefined,
|
|
||||||
});
|
|
||||||
const text = await res.text();
|
|
||||||
if (res.ok) {
|
|
||||||
return text ? (JSON.parse(text) as T) : ({} as T);
|
|
||||||
} else {
|
|
||||||
throw new ApiError(res.status, text);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get uploader for uploading files
|
* Get uploader for uploading files
|
||||||
*/
|
*/
|
||||||
getUploader(
|
getUploader(
|
||||||
file: File | Blob,
|
file: File | Blob,
|
||||||
stateChange?: StateChangeHandler,
|
stateChange?: StateChangeHandler,
|
||||||
progress?: ProgressHandler,
|
progress?: ProgressHandler,
|
||||||
proxyChallenge?: ProxyChallengeHandler,
|
proxyChallenge?: ProxyChallengeHandler,
|
||||||
chunkSize?: number,
|
chunkSize?: number,
|
||||||
): VoidUploader {
|
): VoidUploader {
|
||||||
if (StreamUploader.canUse()) {
|
if (StreamUploader.canUse()) {
|
||||||
return new StreamUploader(
|
return new StreamUploader(
|
||||||
this.#uri,
|
this.#uri,
|
||||||
file,
|
file,
|
||||||
stateChange,
|
stateChange,
|
||||||
progress,
|
progress,
|
||||||
proxyChallenge,
|
proxyChallenge,
|
||||||
this.#auth,
|
this.#auth,
|
||||||
chunkSize,
|
chunkSize,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return new XHRUploader(
|
return new XHRUploader(
|
||||||
this.#uri,
|
this.#uri,
|
||||||
file,
|
file,
|
||||||
stateChange,
|
stateChange,
|
||||||
progress,
|
progress,
|
||||||
proxyChallenge,
|
proxyChallenge,
|
||||||
this.#auth,
|
this.#auth,
|
||||||
chunkSize,
|
chunkSize,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* General site information
|
* General site information
|
||||||
*/
|
*/
|
||||||
info() {
|
info() {
|
||||||
return this.#req<SiteInfoResponse>("GET", "/info");
|
return this.#req<SiteInfoResponse>("GET", "/info");
|
||||||
}
|
}
|
||||||
|
|
||||||
fileInfo(id: string) {
|
fileInfo(id: string) {
|
||||||
return this.#req<VoidFileResponse>("GET", `/upload/${id}`);
|
return this.#req<VoidFileResponse>("GET", `/upload/${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
setPaymentConfig(id: string, cfg: SetPaymentConfigRequest) {
|
setPaymentConfig(id: string, cfg: SetPaymentConfigRequest) {
|
||||||
return this.#req("POST", `/upload/${id}/payment`, cfg);
|
return this.#req("POST", `/upload/${id}/payment`, cfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
createOrder(id: string) {
|
createOrder(id: string) {
|
||||||
return this.#req<PaymentOrder>("GET", `/upload/${id}/payment`);
|
return this.#req<PaymentOrder>("GET", `/upload/${id}/payment`);
|
||||||
}
|
}
|
||||||
|
|
||||||
getOrder(file: string, order: string) {
|
getOrder(file: string, order: string) {
|
||||||
return this.#req<PaymentOrder>("GET", `/upload/${file}/payment/${order}`);
|
return this.#req<PaymentOrder>("GET", `/upload/${file}/payment/${order}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
login(username: string, password: string, captcha?: string) {
|
login(username: string, password: string, captcha?: string) {
|
||||||
return this.#req<LoginSession>("POST", `/auth/login`, {
|
return this.#req<LoginSession>("POST", `/auth/login`, {
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
captcha,
|
captcha,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
register(username: string, password: string, captcha?: string) {
|
register(username: string, password: string, captcha?: string) {
|
||||||
return this.#req<LoginSession>("POST", `/auth/register`, {
|
return this.#req<LoginSession>("POST", `/auth/register`, {
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
captcha,
|
captcha,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getUser(id: string) {
|
getUser(id: string) {
|
||||||
return this.#req<Profile>("GET", `/user/${id}`);
|
return this.#req<Profile>("GET", `/user/${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateUser(u: Profile) {
|
updateUser(u: Profile) {
|
||||||
return this.#req<void>("POST", `/user/${u.id}`, u);
|
return this.#req<void>("POST", `/user/${u.id}`, u);
|
||||||
}
|
}
|
||||||
|
|
||||||
listUserFiles(uid: string, pageReq: PagedRequest) {
|
listUserFiles(uid: string, pageReq: PagedRequest) {
|
||||||
return this.#req<PagedResponse<VoidFileResponse>>(
|
return this.#req<PagedResponse<VoidFileResponse>>(
|
||||||
"POST",
|
"POST",
|
||||||
`/user/${uid}/files`,
|
`/user/${uid}/files`,
|
||||||
pageReq,
|
pageReq,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
submitVerifyCode(uid: string, code: string) {
|
submitVerifyCode(uid: string, code: string) {
|
||||||
return this.#req<void>("POST", `/user/${uid}/verify`, {code});
|
return this.#req<void>("POST", `/user/${uid}/verify`, { code });
|
||||||
}
|
}
|
||||||
|
|
||||||
sendNewCode(uid: string) {
|
sendNewCode(uid: string) {
|
||||||
return this.#req<void>("GET", `/user/${uid}/verify`);
|
return this.#req<void>("GET", `/user/${uid}/verify`);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateFileMetadata(id: string, meta: any) {
|
updateFileMetadata(id: string, meta: any) {
|
||||||
return this.#req<void>("POST", `/upload/${id}/meta`, meta);
|
return this.#req<void>("POST", `/upload/${id}/meta`, meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
listApiKeys() {
|
listApiKeys() {
|
||||||
return this.#req<Array<ApiKey>>("GET", `/auth/api-key`);
|
return this.#req<Array<ApiKey>>("GET", `/auth/api-key`);
|
||||||
}
|
}
|
||||||
|
|
||||||
createApiKey(req: any) {
|
createApiKey(req: any) {
|
||||||
return this.#req<ApiKey>("POST", `/auth/api-key`, req);
|
return this.#req<ApiKey>("POST", `/auth/api-key`, req);
|
||||||
}
|
}
|
||||||
|
|
||||||
adminListFiles(pageReq: PagedRequest) {
|
adminListFiles(pageReq: PagedRequest) {
|
||||||
return this.#req<PagedResponse<VoidFileResponse>>(
|
return this.#req<PagedResponse<VoidFileResponse>>(
|
||||||
"POST",
|
"POST",
|
||||||
"/admin/file",
|
"/admin/file",
|
||||||
pageReq,
|
pageReq,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
adminDeleteFile(id: string) {
|
adminDeleteFile(id: string) {
|
||||||
return this.#req<void>("DELETE", `/admin/file/${id}`);
|
return this.#req<void>("DELETE", `/admin/file/${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
adminUserList(pageReq: PagedRequest) {
|
adminUserList(pageReq: PagedRequest) {
|
||||||
return this.#req<PagedResponse<AdminUserListResult>>(
|
return this.#req<PagedResponse<AdminUserListResult>>(
|
||||||
"POST",
|
"POST",
|
||||||
`/admin/users`,
|
`/admin/users`,
|
||||||
pageReq,
|
pageReq,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
adminUpdateUser(u: AdminProfile) {
|
adminUpdateUser(u: AdminProfile) {
|
||||||
return this.#req<void>("POST", `/admin/update-user`, u);
|
return this.#req<void>("POST", `/admin/update-user`, u);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="/logo_32.png"/>
|
<link rel="icon" href="/logo_32.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#000000"/>
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta name="description" content="void.cat - free, simple file sharing."/>
|
<meta name="description" content="void.cat - free, simple file sharing." />
|
||||||
<link rel="apple-touch-icon" href="/logo_256.png"/>
|
<link rel="apple-touch-icon" href="/logo_256.png" />
|
||||||
<link rel="manifest" href="/manifest.json"/>
|
<link rel="manifest" href="/manifest.json" />
|
||||||
<title>void.cat</title>
|
<title>void.cat</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script src="./src/index.tsx" type="module"></script>
|
<script src="./src/index.tsx" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -34,7 +34,12 @@ const router = createBrowserRouter([
|
|||||||
path: "/u/:id",
|
path: "/u/:id",
|
||||||
loader: async ({ params }: LoaderFunctionArgs) => {
|
loader: async ({ params }: LoaderFunctionArgs) => {
|
||||||
const state = store.getState();
|
const state = store.getState();
|
||||||
const api = new VoidApi(import.meta.env.VITE_API_HOST, state.login.jwt ? () => Promise.resolve(`Bearer ${state.login.jwt}`) : undefined);
|
const api = new VoidApi(
|
||||||
|
import.meta.env.VITE_API_HOST,
|
||||||
|
state.login.jwt
|
||||||
|
? () => Promise.resolve(`Bearer ${state.login.jwt}`)
|
||||||
|
: undefined,
|
||||||
|
);
|
||||||
if (params.id) {
|
if (params.id) {
|
||||||
try {
|
try {
|
||||||
return await api.getUser(params.id);
|
return await api.getUser(params.id);
|
||||||
|
@ -3,8 +3,6 @@ import { CSSProperties } from "react";
|
|||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { Profile } from "@void-cat/api";
|
import { Profile } from "@void-cat/api";
|
||||||
|
|
||||||
import { DefaultAvatar } from "@/Const";
|
|
||||||
|
|
||||||
const DefaultSize = 64;
|
const DefaultSize = 64;
|
||||||
|
|
||||||
interface InlineProfileProps {
|
interface InlineProfileProps {
|
||||||
@ -24,8 +22,8 @@ export function InlineProfile({ profile, options }: InlineProfileProps) {
|
|||||||
...options,
|
...options,
|
||||||
};
|
};
|
||||||
|
|
||||||
let avatarUrl = profile.avatar ?? DefaultAvatar;
|
let avatarUrl = profile.avatar ?? "/logo_256.jpg";
|
||||||
if (!avatarUrl.startsWith("http")) {
|
if (profile.avatar && !avatarUrl.startsWith("http")) {
|
||||||
avatarUrl = `/d/${avatarUrl}`;
|
avatarUrl = `/d/${avatarUrl}`;
|
||||||
}
|
}
|
||||||
let avatarStyles = {
|
let avatarStyles = {
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
export const DefaultAvatar = "https://i.imgur.com/8A5Fu65.jpeg";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @constant {number} - Size of 1 kiB
|
* @constant {number} - Size of 1 kiB
|
||||||
*/
|
*/
|
||||||
|
@ -5,5 +5,8 @@ import { RootState } from "@/Store";
|
|||||||
|
|
||||||
export default function useApi() {
|
export default function useApi() {
|
||||||
const auth = useSelector((s: RootState) => s.login.jwt);
|
const auth = useSelector((s: RootState) => s.login.jwt);
|
||||||
return new VoidApi(import.meta.env.VITE_API_HOST, auth ? () => Promise.resolve(`Bearer ${auth}`) : undefined);
|
return new VoidApi(
|
||||||
|
import.meta.env.VITE_API_HOST,
|
||||||
|
auth ? () => Promise.resolve(`Bearer ${auth}`) : undefined,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -248,7 +248,9 @@ export function FilePreview() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (info) {
|
if (info) {
|
||||||
const fileLink = info.metadata?.url ?? `${import.meta.env.VITE_API_HOST ?? ""}/d/${info.id}`;
|
const fileLink =
|
||||||
|
info.metadata?.url ??
|
||||||
|
`${import.meta.env.VITE_API_HOST ?? ""}/d/${info.id}`;
|
||||||
setFileSize(info.metadata?.size ?? 0);
|
setFileSize(info.metadata?.size ?? 0);
|
||||||
|
|
||||||
const order = window.localStorage.getItem(`payment-${info.id}`);
|
const order = window.localStorage.getItem(`payment-${info.id}`);
|
||||||
|
@ -7,7 +7,6 @@ import { Profile } from "@void-cat/api";
|
|||||||
|
|
||||||
import useApi from "@/Hooks/UseApi";
|
import useApi from "@/Hooks/UseApi";
|
||||||
import { RootState } from "@/Store";
|
import { RootState } from "@/Store";
|
||||||
import { DefaultAvatar } from "@/Const";
|
|
||||||
|
|
||||||
import { logout, setProfile as setGlobalProfile } from "@/LoginState";
|
import { logout, setProfile as setGlobalProfile } from "@/LoginState";
|
||||||
import { FileList } from "@/Components/Shared/FileList";
|
import { FileList } from "@/Components/Shared/FileList";
|
||||||
@ -173,9 +172,8 @@ export function ProfilePage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (profile) {
|
if (profile) {
|
||||||
let avatarUrl = profile.avatar ?? DefaultAvatar;
|
let avatarUrl = profile.avatar ?? "/logo_256.jpg";
|
||||||
if (!avatarUrl.startsWith("http")) {
|
if (profile.avatar && !avatarUrl.startsWith("http")) {
|
||||||
// assume void-cat hosted avatar
|
|
||||||
avatarUrl = `/d/${avatarUrl}`;
|
avatarUrl = `/d/${avatarUrl}`;
|
||||||
}
|
}
|
||||||
let avatarStyles = {
|
let avatarStyles = {
|
||||||
|
@ -1,34 +1,33 @@
|
|||||||
import react from "@vitejs/plugin-react";
|
import react from "@vitejs/plugin-react";
|
||||||
import {visualizer} from "rollup-plugin-visualizer";
|
import { visualizer } from "rollup-plugin-visualizer";
|
||||||
import {defineConfig} from "vite";
|
import { defineConfig } from "vite";
|
||||||
import {vitePluginVersionMark} from "vite-plugin-version-mark";
|
import { vitePluginVersionMark } from "vite-plugin-version-mark";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
react(),
|
react(),
|
||||||
visualizer({
|
visualizer({
|
||||||
open: true,
|
open: true,
|
||||||
gzipSize: true,
|
gzipSize: true,
|
||||||
filename: "build/stats.html",
|
filename: "build/stats.html",
|
||||||
}),
|
}),
|
||||||
vitePluginVersionMark({
|
vitePluginVersionMark({
|
||||||
name: "void_cat",
|
name: "void_cat",
|
||||||
ifGitSHA: true,
|
ifGitSHA: true,
|
||||||
command: "git describe --always --tags",
|
command: "git describe --always --tags",
|
||||||
ifMeta: false,
|
ifMeta: false,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
assetsInclude: [],
|
assetsInclude: [],
|
||||||
build: {
|
build: {
|
||||||
outDir: "build"
|
outDir: "build",
|
||||||
|
},
|
||||||
|
base: "/",
|
||||||
|
clearScreen: false,
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@": "/src",
|
||||||
},
|
},
|
||||||
base: "/",
|
},
|
||||||
clearScreen: false,
|
define: {},
|
||||||
resolve: {
|
|
||||||
alias: {
|
|
||||||
"@": "/src",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
define: {}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user