mirror of
https://github.com/stonith404/pingvin-share.git
synced 2025-02-19 01:55:48 -05:00
refactor: handle authentication state in middleware
This commit is contained in:
parent
064ef38d78
commit
4e840ecd29
17 changed files with 511 additions and 474 deletions
|
@ -120,7 +120,7 @@ export class AuthController {
|
||||||
const accessToken = await this.authService.refreshAccessToken(
|
const accessToken = await this.authService.refreshAccessToken(
|
||||||
request.cookies.refresh_token
|
request.cookies.refresh_token
|
||||||
);
|
);
|
||||||
response.cookie("access_token", accessToken);
|
response = this.addTokensToResponse(response, undefined, accessToken);
|
||||||
return new TokenDTO().from({ accessToken });
|
return new TokenDTO().from({ accessToken });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,11 +162,13 @@ export class AuthController {
|
||||||
refreshToken?: string,
|
refreshToken?: string,
|
||||||
accessToken?: string
|
accessToken?: string
|
||||||
) {
|
) {
|
||||||
if (accessToken) response.cookie("access_token", accessToken);
|
if (accessToken)
|
||||||
|
response.cookie("access_token", accessToken, { sameSite: "lax" });
|
||||||
if (refreshToken)
|
if (refreshToken)
|
||||||
response.cookie("refresh_token", refreshToken, {
|
response.cookie("refresh_token", refreshToken, {
|
||||||
path: "/api/auth/token",
|
path: "/api/auth/token",
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
|
sameSite: "strict",
|
||||||
maxAge: 1000 * 60 * 60 * 24 * 30 * 3,
|
maxAge: 1000 * 60 * 60 * 24 * 30 * 3,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -110,26 +110,30 @@ export class AuthService {
|
||||||
{
|
{
|
||||||
sub: user.id,
|
sub: user.id,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
|
isAdmin: user.isAdmin,
|
||||||
refreshTokenId,
|
refreshTokenId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expiresIn: "15min",
|
expiresIn: "10s",
|
||||||
secret: this.config.get("JWT_SECRET"),
|
secret: this.config.get("JWT_SECRET"),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async signOut(accessToken: string) {
|
async signOut(accessToken: string) {
|
||||||
const { refreshTokenId } = this.jwtService.decode(accessToken) as {
|
const { refreshTokenId } =
|
||||||
refreshTokenId: string;
|
(this.jwtService.decode(accessToken) as {
|
||||||
};
|
refreshTokenId: string;
|
||||||
|
}) || {};
|
||||||
|
|
||||||
await this.prisma.refreshToken
|
if (refreshTokenId) {
|
||||||
.delete({ where: { id: refreshTokenId } })
|
await this.prisma.refreshToken
|
||||||
.catch((e) => {
|
.delete({ where: { id: refreshTokenId } })
|
||||||
// Ignore error if refresh token doesn't exist
|
.catch((e) => {
|
||||||
if (e.code != "P2025") throw e;
|
// Ignore error if refresh token doesn't exist
|
||||||
});
|
if (e.code != "P2025") throw e;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async refreshAccessToken(refreshToken: string) {
|
async refreshAccessToken(refreshToken: string) {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Body, Controller, Get, Patch, Post, UseGuards } from "@nestjs/common";
|
import { Body, Controller, Get, Patch, Post, UseGuards } from "@nestjs/common";
|
||||||
|
import { SkipThrottle } from "@nestjs/throttler";
|
||||||
import { AdministratorGuard } from "src/auth/guard/isAdmin.guard";
|
import { AdministratorGuard } from "src/auth/guard/isAdmin.guard";
|
||||||
import { JwtGuard } from "src/auth/guard/jwt.guard";
|
import { JwtGuard } from "src/auth/guard/jwt.guard";
|
||||||
import { EmailService } from "src/email/email.service";
|
import { EmailService } from "src/email/email.service";
|
||||||
|
@ -16,6 +17,7 @@ export class ConfigController {
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
|
@SkipThrottle()
|
||||||
async list() {
|
async list() {
|
||||||
return new ConfigDTO().fromList(await this.configService.list());
|
return new ConfigDTO().fromList(await this.configService.list());
|
||||||
}
|
}
|
||||||
|
|
11
frontend/package-lock.json
generated
11
frontend/package-lock.json
generated
|
@ -21,6 +21,7 @@
|
||||||
"cookies-next": "^2.1.1",
|
"cookies-next": "^2.1.1",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"jose": "^4.11.2",
|
"jose": "^4.11.2",
|
||||||
|
"jwt-decode": "^3.1.2",
|
||||||
"mime-types": "^2.1.35",
|
"mime-types": "^2.1.35",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"next": "^13.1.2",
|
"next": "^13.1.2",
|
||||||
|
@ -5610,6 +5611,11 @@
|
||||||
"node": ">=4.0"
|
"node": ">=4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jwt-decode": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="
|
||||||
|
},
|
||||||
"node_modules/klona": {
|
"node_modules/klona": {
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz",
|
||||||
|
@ -12122,6 +12128,11 @@
|
||||||
"object.assign": "^4.1.2"
|
"object.assign": "^4.1.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"jwt-decode": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="
|
||||||
|
},
|
||||||
"klona": {
|
"klona": {
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz",
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
"cookies-next": "^2.1.1",
|
"cookies-next": "^2.1.1",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"jose": "^4.11.2",
|
"jose": "^4.11.2",
|
||||||
|
"jwt-decode": "^3.1.2",
|
||||||
"mime-types": "^2.1.35",
|
"mime-types": "^2.1.35",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"next": "^13.1.2",
|
"next": "^13.1.2",
|
||||||
|
|
|
@ -14,7 +14,6 @@ import { useForm, yupResolver } from "@mantine/form";
|
||||||
import { useModals } from "@mantine/modals";
|
import { useModals } from "@mantine/modals";
|
||||||
import { ModalsContextProps } from "@mantine/modals/lib/context";
|
import { ModalsContextProps } from "@mantine/modals/lib/context";
|
||||||
import * as yup from "yup";
|
import * as yup from "yup";
|
||||||
import useUser from "../../hooks/user.hook";
|
|
||||||
import authService from "../../services/auth.service";
|
import authService from "../../services/auth.service";
|
||||||
import toast from "../../utils/toast.util";
|
import toast from "../../utils/toast.util";
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
Title,
|
Title,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { useMediaQuery } from "@mantine/hooks";
|
import { useMediaQuery } from "@mantine/hooks";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import useConfig from "../../../hooks/config.hook";
|
import useConfig from "../../../hooks/config.hook";
|
||||||
import configService from "../../../services/config.service";
|
import configService from "../../../services/config.service";
|
||||||
|
@ -27,6 +28,7 @@ import TestEmailButton from "./TestEmailButton";
|
||||||
|
|
||||||
const AdminConfigTable = () => {
|
const AdminConfigTable = () => {
|
||||||
const config = useConfig();
|
const config = useConfig();
|
||||||
|
const router = useRouter();
|
||||||
const isMobile = useMediaQuery("(max-width: 560px)");
|
const isMobile = useMediaQuery("(max-width: 560px)");
|
||||||
|
|
||||||
const [updatedConfigVariables, setUpdatedConfigVariables] = useState<
|
const [updatedConfigVariables, setUpdatedConfigVariables] = useState<
|
||||||
|
@ -68,7 +70,7 @@ const AdminConfigTable = () => {
|
||||||
.updateMany(updatedConfigVariables)
|
.updateMany(updatedConfigVariables)
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
await configService.finishSetup();
|
await configService.finishSetup();
|
||||||
window.location.reload();
|
router.replace("/upload");
|
||||||
})
|
})
|
||||||
.catch(toast.axiosError);
|
.catch(toast.axiosError);
|
||||||
} else {
|
} else {
|
||||||
|
|
111
frontend/src/middleware.ts
Normal file
111
frontend/src/middleware.ts
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
import jwtDecode from "jwt-decode";
|
||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import configService from "./services/config.service";
|
||||||
|
|
||||||
|
// This middleware redirects based on different conditions:
|
||||||
|
// - Authentication state
|
||||||
|
// - Setup status
|
||||||
|
// - Admin privileges
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
matcher: "/((?!api|static|.*\\..*|_next).*)",
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function middleware(request: NextRequest) {
|
||||||
|
// Get config from backend
|
||||||
|
const config = await (
|
||||||
|
await fetch("http://localhost:8080/api/configs")
|
||||||
|
).json();
|
||||||
|
|
||||||
|
const getConfig = (key: string) => {
|
||||||
|
return configService.get(key, config);
|
||||||
|
};
|
||||||
|
|
||||||
|
const containsRoute = (routes: string[], url: string) => {
|
||||||
|
for (const route of routes) {
|
||||||
|
if (new RegExp("^" + route.replace(/\*/g, ".*") + "$").test(url))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const route = request.nextUrl.pathname;
|
||||||
|
let user: { isAdmin: boolean } | null = null;
|
||||||
|
const accessToken = request.cookies.get("access_token")?.value;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const claims = jwtDecode<{ exp: number; isAdmin: boolean }>(
|
||||||
|
accessToken as string
|
||||||
|
);
|
||||||
|
if (claims.exp * 1000 > Date.now()) {
|
||||||
|
user = claims;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
user = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const unauthenticatedRoutes = ["/auth/signIn", "/"];
|
||||||
|
let publicRoutes = ["/share/*", "/upload/*"];
|
||||||
|
const setupStatusRegisteredRoutes = ["/auth/*", "/admin/setup"];
|
||||||
|
const adminRoutes = ["/admin/*"];
|
||||||
|
const accountRoutes = ["/account/*"];
|
||||||
|
|
||||||
|
if (getConfig("ALLOW_REGISTRATION")) {
|
||||||
|
unauthenticatedRoutes.push("/auth/signUp");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getConfig("ALLOW_UNAUTHENTICATED_SHARES")) {
|
||||||
|
publicRoutes = ["*"];
|
||||||
|
}
|
||||||
|
|
||||||
|
const isPublicRoute = containsRoute(publicRoutes, route);
|
||||||
|
const isUnauthenticatedRoute = containsRoute(unauthenticatedRoutes, route);
|
||||||
|
const isAdminRoute = containsRoute(adminRoutes, route);
|
||||||
|
const isAccountRoute = containsRoute(accountRoutes, route);
|
||||||
|
const isSetupStatusRegisteredRoute = containsRoute(
|
||||||
|
setupStatusRegisteredRoutes,
|
||||||
|
route
|
||||||
|
);
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
const rules = [
|
||||||
|
// Setup status
|
||||||
|
{
|
||||||
|
condition: getConfig("SETUP_STATUS") == "STARTED" && route != "/auth/signUp",
|
||||||
|
path: "/auth/signUp",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
condition: getConfig("SETUP_STATUS") == "REGISTERED" && !isSetupStatusRegisteredRoute,
|
||||||
|
path: user ? "/admin/setup" : "/auth/signIn",
|
||||||
|
},
|
||||||
|
// Authenticated state
|
||||||
|
{
|
||||||
|
condition: user && isUnauthenticatedRoute,
|
||||||
|
path: "/upload",
|
||||||
|
},
|
||||||
|
// Unauthenticated state
|
||||||
|
{
|
||||||
|
condition: !user && !isPublicRoute && !isUnauthenticatedRoute,
|
||||||
|
path: "/auth/signIn",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
condition: !user && isAccountRoute,
|
||||||
|
path: "/upload",
|
||||||
|
},
|
||||||
|
// Admin privileges
|
||||||
|
{
|
||||||
|
condition: isAdminRoute && !user?.isAdmin,
|
||||||
|
path: "/upload",
|
||||||
|
},
|
||||||
|
// Home page
|
||||||
|
{
|
||||||
|
condition: (!getConfig("SHOW_HOME_PAGE") || user) && route == "/",
|
||||||
|
path: "/upload",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const rule of rules) {
|
||||||
|
if (rule.condition)
|
||||||
|
return NextResponse.redirect(new URL(rule.path, request.url));
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,7 +9,6 @@ import { useColorScheme } from "@mantine/hooks";
|
||||||
import { ModalsProvider } from "@mantine/modals";
|
import { ModalsProvider } from "@mantine/modals";
|
||||||
import { NotificationsProvider } from "@mantine/notifications";
|
import { NotificationsProvider } from "@mantine/notifications";
|
||||||
import type { AppProps } from "next/app";
|
import type { AppProps } from "next/app";
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import Header from "../components/navBar/NavBar";
|
import Header from "../components/navBar/NavBar";
|
||||||
import { ConfigContext } from "../hooks/config.hook";
|
import { ConfigContext } from "../hooks/config.hook";
|
||||||
|
@ -26,7 +25,7 @@ import { GlobalLoadingContext } from "../utils/loading.util";
|
||||||
|
|
||||||
function App({ Component, pageProps }: AppProps) {
|
function App({ Component, pageProps }: AppProps) {
|
||||||
const systemTheme = useColorScheme();
|
const systemTheme = useColorScheme();
|
||||||
const router = useRouter();
|
|
||||||
const preferences = usePreferences();
|
const preferences = usePreferences();
|
||||||
const [colorScheme, setColorScheme] = useState<ColorScheme>("light");
|
const [colorScheme, setColorScheme] = useState<ColorScheme>("light");
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
@ -46,25 +45,6 @@ function App({ Component, pageProps }: AppProps) {
|
||||||
getInitalData();
|
getInitalData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Redirect to setup page if setup is not completed
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
configVariables &&
|
|
||||||
!["/auth/signUp", "/admin/setup"].includes(router.asPath)
|
|
||||||
) {
|
|
||||||
const setupStatus = configVariables.filter(
|
|
||||||
(variable) => variable.key == "SETUP_STATUS"
|
|
||||||
)[0].value;
|
|
||||||
if (setupStatus == "STARTED") {
|
|
||||||
router.replace("/auth/signUp");
|
|
||||||
} else if (user && setupStatus == "REGISTERED") {
|
|
||||||
router.replace("/admin/setup");
|
|
||||||
} else if (setupStatus == "REGISTERED") {
|
|
||||||
router.replace("/auth/signIn");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [configVariables, router.asPath]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setColorScheme(
|
setColorScheme(
|
||||||
preferences.get("colorScheme") == "system"
|
preferences.get("colorScheme") == "system"
|
||||||
|
|
|
@ -13,7 +13,6 @@ import {
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { useForm, yupResolver } from "@mantine/form";
|
import { useForm, yupResolver } from "@mantine/form";
|
||||||
import { useModals } from "@mantine/modals";
|
import { useModals } from "@mantine/modals";
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { Tb2Fa } from "react-icons/tb";
|
import { Tb2Fa } from "react-icons/tb";
|
||||||
import * as yup from "yup";
|
import * as yup from "yup";
|
||||||
import showEnableTotpModal from "../../components/account/showEnableTotpModal";
|
import showEnableTotpModal from "../../components/account/showEnableTotpModal";
|
||||||
|
@ -27,7 +26,6 @@ import toast from "../../utils/toast.util";
|
||||||
const Account = () => {
|
const Account = () => {
|
||||||
const { user, setUser } = useUser();
|
const { user, setUser } = useUser();
|
||||||
const modals = useModals();
|
const modals = useModals();
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const accountForm = useForm({
|
const accountForm = useForm({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
|
@ -85,11 +83,6 @@ const Account = () => {
|
||||||
|
|
||||||
const refreshUser = async () => setUser(await userService.getCurrentUser());
|
const refreshUser = async () => setUser(await userService.getCurrentUser());
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
router.push("/");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Meta title="My account" />
|
<Meta title="My account" />
|
||||||
|
@ -171,7 +164,7 @@ const Account = () => {
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
|
|
||||||
<Tabs.Panel value="totp" pt="xs">
|
<Tabs.Panel value="totp" pt="xs">
|
||||||
{user.totpVerified ? (
|
{user!.totpVerified ? (
|
||||||
<>
|
<>
|
||||||
<form
|
<form
|
||||||
onSubmit={disableTotpForm.onSubmit((values) => {
|
onSubmit={disableTotpForm.onSubmit((values) => {
|
||||||
|
|
|
@ -13,7 +13,6 @@ import {
|
||||||
import { useClipboard } from "@mantine/hooks";
|
import { useClipboard } from "@mantine/hooks";
|
||||||
import { useModals } from "@mantine/modals";
|
import { useModals } from "@mantine/modals";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { TbInfoCircle, TbLink, TbPlus, TbTrash } from "react-icons/tb";
|
import { TbInfoCircle, TbLink, TbPlus, TbTrash } from "react-icons/tb";
|
||||||
import showShareLinkModal from "../../components/account/showShareLinkModal";
|
import showShareLinkModal from "../../components/account/showShareLinkModal";
|
||||||
|
@ -21,7 +20,6 @@ import CenterLoader from "../../components/core/CenterLoader";
|
||||||
import Meta from "../../components/Meta";
|
import Meta from "../../components/Meta";
|
||||||
import showCreateReverseShareModal from "../../components/share/modals/showCreateReverseShareModal";
|
import showCreateReverseShareModal from "../../components/share/modals/showCreateReverseShareModal";
|
||||||
import useConfig from "../../hooks/config.hook";
|
import useConfig from "../../hooks/config.hook";
|
||||||
import useUser from "../../hooks/user.hook";
|
|
||||||
import shareService from "../../services/share.service";
|
import shareService from "../../services/share.service";
|
||||||
import { MyReverseShare } from "../../types/share.type";
|
import { MyReverseShare } from "../../types/share.type";
|
||||||
import { byteToHumanSizeString } from "../../utils/fileSize.util";
|
import { byteToHumanSizeString } from "../../utils/fileSize.util";
|
||||||
|
@ -30,10 +28,8 @@ import toast from "../../utils/toast.util";
|
||||||
const MyShares = () => {
|
const MyShares = () => {
|
||||||
const modals = useModals();
|
const modals = useModals();
|
||||||
const clipboard = useClipboard();
|
const clipboard = useClipboard();
|
||||||
const router = useRouter();
|
|
||||||
const config = useConfig();
|
|
||||||
|
|
||||||
const { user } = useUser();
|
const config = useConfig();
|
||||||
|
|
||||||
const [reverseShares, setReverseShares] = useState<MyReverseShare[]>();
|
const [reverseShares, setReverseShares] = useState<MyReverseShare[]>();
|
||||||
|
|
||||||
|
@ -47,154 +43,145 @@ const MyShares = () => {
|
||||||
getReverseShares();
|
getReverseShares();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (!user) {
|
if (!reverseShares) return <CenterLoader />;
|
||||||
router.replace("/");
|
return (
|
||||||
} else {
|
<>
|
||||||
if (!reverseShares) return <CenterLoader />;
|
<Meta title="My shares" />
|
||||||
return (
|
<Group position="apart" align="baseline" mb={20}>
|
||||||
<>
|
<Group align="center" spacing={3} mb={30}>
|
||||||
<Meta title="My shares" />
|
<Title order={3}>My reverse shares</Title>
|
||||||
<Group position="apart" align="baseline" mb={20}>
|
<Tooltip
|
||||||
<Group align="center" spacing={3} mb={30}>
|
position="bottom"
|
||||||
<Title order={3}>My reverse shares</Title>
|
multiline
|
||||||
<Tooltip
|
width={220}
|
||||||
position="bottom"
|
label="A reverse share allows you to generate a unique URL for a single-use share for an external user."
|
||||||
multiline
|
events={{ hover: true, focus: false, touch: true }}
|
||||||
width={220}
|
|
||||||
label="A reverse share allows you to generate a unique URL for a single-use share for an external user."
|
|
||||||
events={{ hover: true, focus: false, touch: true }}
|
|
||||||
>
|
|
||||||
<ActionIcon>
|
|
||||||
<TbInfoCircle />
|
|
||||||
</ActionIcon>
|
|
||||||
</Tooltip>
|
|
||||||
</Group>
|
|
||||||
<Button
|
|
||||||
onClick={() =>
|
|
||||||
showCreateReverseShareModal(
|
|
||||||
modals,
|
|
||||||
config.get("SMTP_ENABLED"),
|
|
||||||
getReverseShares
|
|
||||||
)
|
|
||||||
}
|
|
||||||
leftIcon={<TbPlus size={20} />}
|
|
||||||
>
|
>
|
||||||
Create
|
<ActionIcon>
|
||||||
</Button>
|
<TbInfoCircle />
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
</Group>
|
</Group>
|
||||||
{reverseShares.length == 0 ? (
|
<Button
|
||||||
<Center style={{ height: "70vh" }}>
|
onClick={() =>
|
||||||
<Stack align="center" spacing={10}>
|
showCreateReverseShareModal(
|
||||||
<Title order={3}>It's empty here 👀</Title>
|
modals,
|
||||||
<Text>You don't have any reverse shares.</Text>
|
config.get("SMTP_ENABLED"),
|
||||||
</Stack>
|
getReverseShares
|
||||||
</Center>
|
)
|
||||||
) : (
|
}
|
||||||
<Box sx={{ display: "block", overflowX: "auto" }}>
|
leftIcon={<TbPlus size={20} />}
|
||||||
<Table>
|
>
|
||||||
<thead>
|
Create
|
||||||
<tr>
|
</Button>
|
||||||
<th>Name</th>
|
</Group>
|
||||||
<th>Visitors</th>
|
{reverseShares.length == 0 ? (
|
||||||
<th>Max share size</th>
|
<Center style={{ height: "70vh" }}>
|
||||||
<th>Expires at</th>
|
<Stack align="center" spacing={10}>
|
||||||
<th></th>
|
<Title order={3}>It's empty here 👀</Title>
|
||||||
</tr>
|
<Text>You don't have any reverse shares.</Text>
|
||||||
</thead>
|
</Stack>
|
||||||
<tbody>
|
</Center>
|
||||||
{reverseShares.map((reverseShare) => (
|
) : (
|
||||||
<tr key={reverseShare.id}>
|
<Box sx={{ display: "block", overflowX: "auto" }}>
|
||||||
<td>
|
<Table>
|
||||||
{reverseShare.share ? (
|
<thead>
|
||||||
reverseShare.share?.id
|
<tr>
|
||||||
) : (
|
<th>Name</th>
|
||||||
<Text color="dimmed">No share created yet</Text>
|
<th>Visitors</th>
|
||||||
)}
|
<th>Max share size</th>
|
||||||
</td>
|
<th>Expires at</th>
|
||||||
<td>{reverseShare.share?.views ?? "0"}</td>
|
<th></th>
|
||||||
<td>
|
</tr>
|
||||||
{byteToHumanSizeString(
|
</thead>
|
||||||
parseInt(reverseShare.maxShareSize)
|
<tbody>
|
||||||
)}
|
{reverseShares.map((reverseShare) => (
|
||||||
</td>
|
<tr key={reverseShare.id}>
|
||||||
<td>
|
<td>
|
||||||
{moment(reverseShare.shareExpiration).unix() === 0
|
{reverseShare.share ? (
|
||||||
? "Never"
|
reverseShare.share?.id
|
||||||
: moment(reverseShare.shareExpiration).format("LLL")}
|
) : (
|
||||||
</td>
|
<Text color="dimmed">No share created yet</Text>
|
||||||
<td>
|
)}
|
||||||
<Group position="right">
|
</td>
|
||||||
{reverseShare.share && (
|
<td>{reverseShare.share?.views ?? "0"}</td>
|
||||||
<ActionIcon
|
<td>
|
||||||
color="victoria"
|
{byteToHumanSizeString(parseInt(reverseShare.maxShareSize))}
|
||||||
variant="light"
|
</td>
|
||||||
size={25}
|
<td>
|
||||||
onClick={() => {
|
{moment(reverseShare.shareExpiration).unix() === 0
|
||||||
if (window.isSecureContext) {
|
? "Never"
|
||||||
clipboard.copy(
|
: moment(reverseShare.shareExpiration).format("LLL")}
|
||||||
`${config.get("APP_URL")}/share/${
|
</td>
|
||||||
reverseShare.share!.id
|
<td>
|
||||||
}`
|
<Group position="right">
|
||||||
);
|
{reverseShare.share && (
|
||||||
toast.success(
|
|
||||||
"The share link was copied to the keyboard."
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
showShareLinkModal(
|
|
||||||
modals,
|
|
||||||
reverseShare.share!.id,
|
|
||||||
config.get("APP_URL")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TbLink />
|
|
||||||
</ActionIcon>
|
|
||||||
)}
|
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
color="red"
|
color="victoria"
|
||||||
variant="light"
|
variant="light"
|
||||||
size={25}
|
size={25}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
modals.openConfirmModal({
|
if (window.isSecureContext) {
|
||||||
title: `Delete reverse share`,
|
clipboard.copy(
|
||||||
children: (
|
`${config.get("APP_URL")}/share/${
|
||||||
<Text size="sm">
|
reverseShare.share!.id
|
||||||
Do you really want to delete this reverse
|
}`
|
||||||
share? If you do, the share will be deleted as
|
);
|
||||||
well.
|
toast.success(
|
||||||
</Text>
|
"The share link was copied to the keyboard."
|
||||||
),
|
);
|
||||||
confirmProps: {
|
} else {
|
||||||
color: "red",
|
showShareLinkModal(
|
||||||
},
|
modals,
|
||||||
labels: { confirm: "Confirm", cancel: "Cancel" },
|
reverseShare.share!.id,
|
||||||
onConfirm: () => {
|
config.get("APP_URL")
|
||||||
shareService.removeReverseShare(
|
);
|
||||||
reverseShare.id
|
}
|
||||||
);
|
|
||||||
setReverseShares(
|
|
||||||
reverseShares.filter(
|
|
||||||
(item) => item.id !== reverseShare.id
|
|
||||||
)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TbTrash />
|
<TbLink />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Group>
|
)}
|
||||||
</td>
|
<ActionIcon
|
||||||
</tr>
|
color="red"
|
||||||
))}
|
variant="light"
|
||||||
</tbody>
|
size={25}
|
||||||
</Table>
|
onClick={() => {
|
||||||
</Box>
|
modals.openConfirmModal({
|
||||||
)}
|
title: `Delete reverse share`,
|
||||||
</>
|
children: (
|
||||||
);
|
<Text size="sm">
|
||||||
}
|
Do you really want to delete this reverse share?
|
||||||
|
If you do, the share will be deleted as well.
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
confirmProps: {
|
||||||
|
color: "red",
|
||||||
|
},
|
||||||
|
labels: { confirm: "Confirm", cancel: "Cancel" },
|
||||||
|
onConfirm: () => {
|
||||||
|
shareService.removeReverseShare(reverseShare.id);
|
||||||
|
setReverseShares(
|
||||||
|
reverseShares.filter(
|
||||||
|
(item) => item.id !== reverseShare.id
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TbTrash />
|
||||||
|
</ActionIcon>
|
||||||
|
</Group>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MyShares;
|
export default MyShares;
|
||||||
|
|
|
@ -15,13 +15,11 @@ import { useClipboard } from "@mantine/hooks";
|
||||||
import { useModals } from "@mantine/modals";
|
import { useModals } from "@mantine/modals";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { TbLink, TbTrash } from "react-icons/tb";
|
import { TbLink, TbTrash } from "react-icons/tb";
|
||||||
import showShareLinkModal from "../../components/account/showShareLinkModal";
|
import showShareLinkModal from "../../components/account/showShareLinkModal";
|
||||||
import Meta from "../../components/Meta";
|
import Meta from "../../components/Meta";
|
||||||
import useConfig from "../../hooks/config.hook";
|
import useConfig from "../../hooks/config.hook";
|
||||||
import useUser from "../../hooks/user.hook";
|
|
||||||
import shareService from "../../services/share.service";
|
import shareService from "../../services/share.service";
|
||||||
import { MyShare } from "../../types/share.type";
|
import { MyShare } from "../../types/share.type";
|
||||||
import toast from "../../utils/toast.util";
|
import toast from "../../utils/toast.util";
|
||||||
|
@ -29,122 +27,115 @@ import toast from "../../utils/toast.util";
|
||||||
const MyShares = () => {
|
const MyShares = () => {
|
||||||
const modals = useModals();
|
const modals = useModals();
|
||||||
const clipboard = useClipboard();
|
const clipboard = useClipboard();
|
||||||
const router = useRouter();
|
|
||||||
const config = useConfig();
|
const config = useConfig();
|
||||||
|
|
||||||
const { user } = useUser();
|
|
||||||
|
|
||||||
const [shares, setShares] = useState<MyShare[]>();
|
const [shares, setShares] = useState<MyShare[]>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
shareService.getMyShares().then((shares) => setShares(shares));
|
shareService.getMyShares().then((shares) => setShares(shares));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (!user) {
|
if (!shares) return <LoadingOverlay visible />;
|
||||||
router.replace("/");
|
return (
|
||||||
} else {
|
<>
|
||||||
if (!shares) return <LoadingOverlay visible />;
|
<Meta title="My shares" />
|
||||||
return (
|
<Title mb={30} order={3}>
|
||||||
<>
|
My shares
|
||||||
<Meta title="My shares" />
|
</Title>
|
||||||
<Title mb={30} order={3}>
|
{shares.length == 0 ? (
|
||||||
My shares
|
<Center style={{ height: "70vh" }}>
|
||||||
</Title>
|
<Stack align="center" spacing={10}>
|
||||||
{shares.length == 0 ? (
|
<Title order={3}>It's empty here 👀</Title>
|
||||||
<Center style={{ height: "70vh" }}>
|
<Text>You don't have any shares.</Text>
|
||||||
<Stack align="center" spacing={10}>
|
<Space h={5} />
|
||||||
<Title order={3}>It's empty here 👀</Title>
|
<Button component={Link} href="/upload" variant="light">
|
||||||
<Text>You don't have any shares.</Text>
|
Create one
|
||||||
<Space h={5} />
|
</Button>
|
||||||
<Button component={Link} href="/upload" variant="light">
|
</Stack>
|
||||||
Create one
|
</Center>
|
||||||
</Button>
|
) : (
|
||||||
</Stack>
|
<Box sx={{ display: "block", overflowX: "auto" }}>
|
||||||
</Center>
|
<Table>
|
||||||
) : (
|
<thead>
|
||||||
<Box sx={{ display: "block", overflowX: "auto" }}>
|
<tr>
|
||||||
<Table>
|
<th>Name</th>
|
||||||
<thead>
|
<th>Visitors</th>
|
||||||
<tr>
|
<th>Expires at</th>
|
||||||
<th>Name</th>
|
<th></th>
|
||||||
<th>Visitors</th>
|
</tr>
|
||||||
<th>Expires at</th>
|
</thead>
|
||||||
<th></th>
|
<tbody>
|
||||||
|
{shares.map((share) => (
|
||||||
|
<tr key={share.id}>
|
||||||
|
<td>{share.id}</td>
|
||||||
|
<td>{share.views}</td>
|
||||||
|
<td>
|
||||||
|
{moment(share.expiration).unix() === 0
|
||||||
|
? "Never"
|
||||||
|
: moment(share.expiration).format("LLL")}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<Group position="right">
|
||||||
|
<ActionIcon
|
||||||
|
color="victoria"
|
||||||
|
variant="light"
|
||||||
|
size={25}
|
||||||
|
onClick={() => {
|
||||||
|
if (window.isSecureContext) {
|
||||||
|
clipboard.copy(
|
||||||
|
`${config.get("APP_URL")}/share/${share.id}`
|
||||||
|
);
|
||||||
|
toast.success(
|
||||||
|
"Your link was copied to the keyboard."
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
showShareLinkModal(
|
||||||
|
modals,
|
||||||
|
share.id,
|
||||||
|
config.get("APP_URL")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TbLink />
|
||||||
|
</ActionIcon>
|
||||||
|
<ActionIcon
|
||||||
|
color="red"
|
||||||
|
variant="light"
|
||||||
|
size={25}
|
||||||
|
onClick={() => {
|
||||||
|
modals.openConfirmModal({
|
||||||
|
title: `Delete share ${share.id}`,
|
||||||
|
children: (
|
||||||
|
<Text size="sm">
|
||||||
|
Do you really want to delete this share?
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
confirmProps: {
|
||||||
|
color: "red",
|
||||||
|
},
|
||||||
|
labels: { confirm: "Confirm", cancel: "Cancel" },
|
||||||
|
onConfirm: () => {
|
||||||
|
shareService.remove(share.id);
|
||||||
|
setShares(
|
||||||
|
shares.filter((item) => item.id !== share.id)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TbTrash />
|
||||||
|
</ActionIcon>
|
||||||
|
</Group>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
))}
|
||||||
<tbody>
|
</tbody>
|
||||||
{shares.map((share) => (
|
</Table>
|
||||||
<tr key={share.id}>
|
</Box>
|
||||||
<td>{share.id}</td>
|
)}
|
||||||
<td>{share.views}</td>
|
</>
|
||||||
<td>
|
);
|
||||||
{moment(share.expiration).unix() === 0
|
|
||||||
? "Never"
|
|
||||||
: moment(share.expiration).format("LLL")}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<Group position="right">
|
|
||||||
<ActionIcon
|
|
||||||
color="victoria"
|
|
||||||
variant="light"
|
|
||||||
size={25}
|
|
||||||
onClick={() => {
|
|
||||||
if (window.isSecureContext) {
|
|
||||||
clipboard.copy(
|
|
||||||
`${config.get("APP_URL")}/share/${share.id}`
|
|
||||||
);
|
|
||||||
toast.success(
|
|
||||||
"Your link was copied to the keyboard."
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
showShareLinkModal(
|
|
||||||
modals,
|
|
||||||
share.id,
|
|
||||||
config.get("APP_URL")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TbLink />
|
|
||||||
</ActionIcon>
|
|
||||||
<ActionIcon
|
|
||||||
color="red"
|
|
||||||
variant="light"
|
|
||||||
size={25}
|
|
||||||
onClick={() => {
|
|
||||||
modals.openConfirmModal({
|
|
||||||
title: `Delete share ${share.id}`,
|
|
||||||
children: (
|
|
||||||
<Text size="sm">
|
|
||||||
Do you really want to delete this share?
|
|
||||||
</Text>
|
|
||||||
),
|
|
||||||
confirmProps: {
|
|
||||||
color: "red",
|
|
||||||
},
|
|
||||||
labels: { confirm: "Confirm", cancel: "Cancel" },
|
|
||||||
onConfirm: () => {
|
|
||||||
shareService.remove(share.id);
|
|
||||||
setShares(
|
|
||||||
shares.filter((item) => item.id !== share.id)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TbTrash />
|
|
||||||
</ActionIcon>
|
|
||||||
</Group>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</Table>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MyShares;
|
export default MyShares;
|
||||||
|
|
|
@ -1,25 +1,10 @@
|
||||||
import { Box, Stack, Text, Title } from "@mantine/core";
|
import { Box, Stack, Text, Title } from "@mantine/core";
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import AdminConfigTable from "../../components/admin/configuration/AdminConfigTable";
|
import AdminConfigTable from "../../components/admin/configuration/AdminConfigTable";
|
||||||
|
|
||||||
import Logo from "../../components/Logo";
|
import Logo from "../../components/Logo";
|
||||||
import Meta from "../../components/Meta";
|
import Meta from "../../components/Meta";
|
||||||
import useConfig from "../../hooks/config.hook";
|
|
||||||
import useUser from "../../hooks/user.hook";
|
|
||||||
|
|
||||||
const Setup = () => {
|
const Setup = () => {
|
||||||
const router = useRouter();
|
|
||||||
const config = useConfig();
|
|
||||||
const { user } = useUser();
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
router.push("/auth/signUp");
|
|
||||||
return;
|
|
||||||
} else if (config.get("SETUP_STATUS") == "FINISHED") {
|
|
||||||
router.push("/");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Meta title="Setup" />
|
<Meta title="Setup" />
|
||||||
|
|
|
@ -1,20 +1,25 @@
|
||||||
|
import { LoadingOverlay } from "@mantine/core";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import SignInForm from "../../components/auth/SignInForm";
|
import SignInForm from "../../components/auth/SignInForm";
|
||||||
import Meta from "../../components/Meta";
|
import Meta from "../../components/Meta";
|
||||||
import useUser from "../../hooks/user.hook";
|
import useUser from "../../hooks/user.hook";
|
||||||
|
|
||||||
const SignIn = () => {
|
const SignIn = () => {
|
||||||
const { user } = useUser();
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { user } = useUser();
|
||||||
|
|
||||||
|
// If the access token is expired, the middleware redirects to this page.
|
||||||
|
// If the refresh token is still valid, the user will be redirected to the home page.
|
||||||
if (user) {
|
if (user) {
|
||||||
router.replace("/");
|
router.replace("/");
|
||||||
} else {
|
return <LoadingOverlay overlayOpacity={1} visible />;
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Meta title="Sign In" />
|
|
||||||
<SignInForm />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Meta title="Sign In" />
|
||||||
|
<SignInForm />
|
||||||
|
</>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
export default SignIn;
|
export default SignIn;
|
||||||
|
|
|
@ -1,24 +1,12 @@
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import SignUpForm from "../../components/auth/SignUpForm";
|
import SignUpForm from "../../components/auth/SignUpForm";
|
||||||
import Meta from "../../components/Meta";
|
import Meta from "../../components/Meta";
|
||||||
import useConfig from "../../hooks/config.hook";
|
|
||||||
import useUser from "../../hooks/user.hook";
|
|
||||||
|
|
||||||
const SignUp = () => {
|
const SignUp = () => {
|
||||||
const config = useConfig();
|
return (
|
||||||
const { user } = useUser();
|
<>
|
||||||
const router = useRouter();
|
<Meta title="Sign Up" />
|
||||||
if (user) {
|
<SignUpForm />
|
||||||
router.replace("/");
|
</>
|
||||||
} else if (!config.get("ALLOW_REGISTRATION")) {
|
);
|
||||||
router.replace("/auth/signIn");
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Meta title="Sign Up" />
|
|
||||||
<SignUpForm />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
export default SignUp;
|
export default SignUp;
|
||||||
|
|
|
@ -10,11 +10,8 @@ import {
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { TbCheck } from "react-icons/tb";
|
import { TbCheck } from "react-icons/tb";
|
||||||
import Meta from "../components/Meta";
|
import Meta from "../components/Meta";
|
||||||
import useConfig from "../hooks/config.hook";
|
|
||||||
import useUser from "../hooks/user.hook";
|
|
||||||
|
|
||||||
const useStyles = createStyles((theme) => ({
|
const useStyles = createStyles((theme) => ({
|
||||||
inner: {
|
inner: {
|
||||||
|
@ -69,94 +66,85 @@ const useStyles = createStyles((theme) => ({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const config = useConfig();
|
|
||||||
const { user } = useUser();
|
|
||||||
|
|
||||||
const { classes } = useStyles();
|
const { classes } = useStyles();
|
||||||
const router = useRouter();
|
|
||||||
if (user || config.get("ALLOW_UNAUTHENTICATED_SHARES")) {
|
|
||||||
router.replace("/upload");
|
|
||||||
} else if (!config.get("SHOW_HOME_PAGE")) {
|
|
||||||
router.replace("/auth/signIn");
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Meta title="Home" />
|
|
||||||
<Container>
|
|
||||||
<div className={classes.inner}>
|
|
||||||
<div className={classes.content}>
|
|
||||||
<Title className={classes.title}>
|
|
||||||
A <span className={classes.highlight}>self-hosted</span> <br />{" "}
|
|
||||||
file sharing platform.
|
|
||||||
</Title>
|
|
||||||
<Text color="dimmed" mt="md">
|
|
||||||
Do you really want to give your personal files in the hand of
|
|
||||||
third parties like WeTransfer?
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<List
|
return (
|
||||||
mt={30}
|
<>
|
||||||
spacing="sm"
|
<Meta title="Home" />
|
||||||
size="sm"
|
<Container>
|
||||||
icon={
|
<div className={classes.inner}>
|
||||||
<ThemeIcon size={20} radius="xl">
|
<div className={classes.content}>
|
||||||
<TbCheck size={12} />
|
<Title className={classes.title}>
|
||||||
</ThemeIcon>
|
A <span className={classes.highlight}>self-hosted</span> <br />{" "}
|
||||||
}
|
file sharing platform.
|
||||||
|
</Title>
|
||||||
|
<Text color="dimmed" mt="md">
|
||||||
|
Do you really want to give your personal files in the hand of
|
||||||
|
third parties like WeTransfer?
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<List
|
||||||
|
mt={30}
|
||||||
|
spacing="sm"
|
||||||
|
size="sm"
|
||||||
|
icon={
|
||||||
|
<ThemeIcon size={20} radius="xl">
|
||||||
|
<TbCheck size={12} />
|
||||||
|
</ThemeIcon>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<List.Item>
|
||||||
|
<div>
|
||||||
|
<b>Self-Hosted</b> - Host Pingvin Share on your own machine.
|
||||||
|
</div>
|
||||||
|
</List.Item>
|
||||||
|
<List.Item>
|
||||||
|
<div>
|
||||||
|
<b>Privacy</b> - Your files are your files and should never
|
||||||
|
get into the hands of third parties.
|
||||||
|
</div>
|
||||||
|
</List.Item>
|
||||||
|
<List.Item>
|
||||||
|
<div>
|
||||||
|
<b>No annoying file size limit</b> - Upload as big files as
|
||||||
|
you want. Only your hard drive will be your limit.
|
||||||
|
</div>
|
||||||
|
</List.Item>
|
||||||
|
</List>
|
||||||
|
|
||||||
|
<Group mt={30}>
|
||||||
|
<Button
|
||||||
|
component={Link}
|
||||||
|
href="/auth/signUp"
|
||||||
|
radius="xl"
|
||||||
|
size="md"
|
||||||
|
className={classes.control}
|
||||||
>
|
>
|
||||||
<List.Item>
|
Get started
|
||||||
<div>
|
</Button>
|
||||||
<b>Self-Hosted</b> - Host Pingvin Share on your own machine.
|
<Button
|
||||||
</div>
|
component={Link}
|
||||||
</List.Item>
|
href="https://github.com/stonith404/pingvin-share"
|
||||||
<List.Item>
|
target="_blank"
|
||||||
<div>
|
variant="default"
|
||||||
<b>Privacy</b> - Your files are your files and should never
|
radius="xl"
|
||||||
get into the hands of third parties.
|
size="md"
|
||||||
</div>
|
className={classes.control}
|
||||||
</List.Item>
|
>
|
||||||
<List.Item>
|
Source code
|
||||||
<div>
|
</Button>
|
||||||
<b>No annoying file size limit</b> - Upload as big files as
|
|
||||||
you want. Only your hard drive will be your limit.
|
|
||||||
</div>
|
|
||||||
</List.Item>
|
|
||||||
</List>
|
|
||||||
|
|
||||||
<Group mt={30}>
|
|
||||||
<Button
|
|
||||||
component={Link}
|
|
||||||
href="/auth/signUp"
|
|
||||||
radius="xl"
|
|
||||||
size="md"
|
|
||||||
className={classes.control}
|
|
||||||
>
|
|
||||||
Get started
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
component={Link}
|
|
||||||
href="https://github.com/stonith404/pingvin-share"
|
|
||||||
target="_blank"
|
|
||||||
variant="default"
|
|
||||||
radius="xl"
|
|
||||||
size="md"
|
|
||||||
className={classes.control}
|
|
||||||
>
|
|
||||||
Source code
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
</div>
|
|
||||||
<Group className={classes.image} align="center">
|
|
||||||
<Image
|
|
||||||
src="/img/logo.svg"
|
|
||||||
alt="Pingvin Share Logo"
|
|
||||||
width={200}
|
|
||||||
height={200}
|
|
||||||
/>
|
|
||||||
</Group>
|
</Group>
|
||||||
</div>
|
</div>
|
||||||
</Container>
|
<Group className={classes.image} align="center">
|
||||||
</>
|
<Image
|
||||||
);
|
src="/img/logo.svg"
|
||||||
}
|
alt="Pingvin Share Logo"
|
||||||
|
width={200}
|
||||||
|
height={200}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,6 @@ import { Button, Group } from "@mantine/core";
|
||||||
import { useModals } from "@mantine/modals";
|
import { useModals } from "@mantine/modals";
|
||||||
import { cleanNotifications } from "@mantine/notifications";
|
import { cleanNotifications } from "@mantine/notifications";
|
||||||
import { AxiosError } from "axios";
|
import { AxiosError } from "axios";
|
||||||
import { getCookie } from "cookies-next";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import pLimit from "p-limit";
|
import pLimit from "p-limit";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import Meta from "../../components/Meta";
|
import Meta from "../../components/Meta";
|
||||||
|
@ -30,7 +28,6 @@ const Upload = ({
|
||||||
maxShareSize?: number;
|
maxShareSize?: number;
|
||||||
isReverseShare: boolean;
|
isReverseShare: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const router = useRouter();
|
|
||||||
const modals = useModals();
|
const modals = useModals();
|
||||||
|
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
|
@ -158,51 +155,42 @@ const Upload = ({
|
||||||
}
|
}
|
||||||
}, [files]);
|
}, [files]);
|
||||||
|
|
||||||
if (
|
return (
|
||||||
!user &&
|
<>
|
||||||
!config.get("ALLOW_UNAUTHENTICATED_SHARES") &&
|
<Meta title="Upload" />
|
||||||
!getCookie("reverse_share_token")
|
<Group position="right" mb={20}>
|
||||||
) {
|
<Button
|
||||||
router.replace("/");
|
loading={isUploading}
|
||||||
return null;
|
disabled={files.length <= 0}
|
||||||
} else {
|
onClick={() => {
|
||||||
return (
|
showCreateUploadModal(
|
||||||
<>
|
modals,
|
||||||
<Meta title="Upload" />
|
{
|
||||||
<Group position="right" mb={20}>
|
isUserSignedIn: user ? true : false,
|
||||||
<Button
|
isReverseShare,
|
||||||
loading={isUploading}
|
appUrl: config.get("APP_URL"),
|
||||||
disabled={files.length <= 0}
|
allowUnauthenticatedShares: config.get(
|
||||||
onClick={() => {
|
"ALLOW_UNAUTHENTICATED_SHARES"
|
||||||
showCreateUploadModal(
|
),
|
||||||
modals,
|
enableEmailRecepients: config.get(
|
||||||
{
|
"ENABLE_SHARE_EMAIL_RECIPIENTS"
|
||||||
isUserSignedIn: user ? true : false,
|
),
|
||||||
isReverseShare,
|
},
|
||||||
appUrl: config.get("APP_URL"),
|
uploadFiles
|
||||||
allowUnauthenticatedShares: config.get(
|
);
|
||||||
"ALLOW_UNAUTHENTICATED_SHARES"
|
}}
|
||||||
),
|
>
|
||||||
enableEmailRecepients: config.get(
|
Share
|
||||||
"ENABLE_SHARE_EMAIL_RECIPIENTS"
|
</Button>
|
||||||
),
|
</Group>
|
||||||
},
|
<Dropzone
|
||||||
uploadFiles
|
maxShareSize={maxShareSize}
|
||||||
);
|
files={files}
|
||||||
}}
|
setFiles={setFiles}
|
||||||
>
|
isUploading={isUploading}
|
||||||
Share
|
/>
|
||||||
</Button>
|
{files.length > 0 && <FileList files={files} setFiles={setFiles} />}
|
||||||
</Group>
|
</>
|
||||||
<Dropzone
|
);
|
||||||
maxShareSize={maxShareSize}
|
|
||||||
files={files}
|
|
||||||
setFiles={setFiles}
|
|
||||||
isUploading={isUploading}
|
|
||||||
/>
|
|
||||||
{files.length > 0 && <FileList files={files} setFiles={setFiles} />}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
export default Upload;
|
export default Upload;
|
||||||
|
|
Loading…
Add table
Reference in a new issue