feat: allow multiple shares with one reverse share link

This commit is contained in:
Elias Schneider
2023-02-10 11:10:07 +01:00
parent edc10b72b7
commit ccdf8ea3ae
12 changed files with 171 additions and 75 deletions

View File

@@ -47,6 +47,7 @@ const Body = ({
const form = useForm({
initialValues: {
maxShareSize: 104857600,
maxUseCount: 1,
sendEmailNotification: false,
expiration_num: 1,
expiration_unit: "-days",
@@ -60,6 +61,7 @@ const Body = ({
.createReverseShare(
values.expiration_num + values.expiration_unit,
values.maxShareSize,
values.maxUseCount,
values.sendEmailNotification
)
.then(({ link }) => {
@@ -132,6 +134,15 @@ const Body = ({
value={form.values.maxShareSize}
onChange={(number) => form.setFieldValue("maxShareSize", number)}
/>
<NumberInput
min={1}
max={1000}
precision={0}
variant="filled"
label="Max use count"
description="The maximum number of times this reverse share link can be used"
{...form.getInputProps("maxUseCount")}
/>
{showSendEmailNotificationOption && (
<Switch
mt="xs"

View File

@@ -13,12 +13,12 @@ export const config = {
export async function middleware(request: NextRequest) {
const routes = {
unauthenticated: new Routes(["/auth/signIn", "/auth/resetPassword*", "/"]),
unauthenticated: new Routes(["/auth/*", "/"]),
public: new Routes(["/share/*", "/upload/*"]),
setupStatusRegistered: new Routes(["/auth/*", "/admin/setup"]),
admin: new Routes(["/admin/*"]),
account: new Routes(["/account/*"]),
disabledRoutes: new Routes([]),
disabled: new Routes([]),
};
// Get config from backend
@@ -46,7 +46,7 @@ export async function middleware(request: NextRequest) {
}
if (!getConfig("ALLOW_REGISTRATION")) {
routes.disabledRoutes.routes.push("/auth/signUp");
routes.disabled.routes.push("/auth/signUp");
}
if (getConfig("ALLOW_UNAUTHENTICATED_SHARES")) {
@@ -54,14 +54,14 @@ export async function middleware(request: NextRequest) {
}
if (!getConfig("SMTP_ENABLED")) {
routes.disabledRoutes.routes.push("/auth/resetPassword*");
routes.disabled.routes.push("/auth/resetPassword*");
}
// prettier-ignore
const rules = [
// Disabled routes
{
condition: routes.disabledRoutes.contains(route),
condition: routes.disabled.contains(route),
path: "/",
},
// Setup status

View File

@@ -1,4 +1,5 @@
import {
Accordion,
ActionIcon,
Box,
Button,
@@ -54,7 +55,7 @@ const MyShares = () => {
position="bottom"
multiline
width={220}
label="A reverse share allows you to generate a unique URL for a single-use share for an external user."
label="A reverse share allows you to generate a unique URL that allows external users to create a share."
events={{ hover: true, focus: false, touch: true }}
>
<ActionIcon>
@@ -87,8 +88,8 @@ const MyShares = () => {
<Table>
<thead>
<tr>
<th>Name</th>
<th>Visitors</th>
<th>Shares</th>
<th>Remaining uses</th>
<th>Max share size</th>
<th>Expires at</th>
<th></th>
@@ -97,14 +98,63 @@ const MyShares = () => {
<tbody>
{reverseShares.map((reverseShare) => (
<tr key={reverseShare.id}>
<td>
{reverseShare.share ? (
reverseShare.share?.id
<td style={{ width: 220 }}>
{reverseShare.shares.length == 0 ? (
<Text color="dimmed" size="sm">
No shares created yet
</Text>
) : (
<Text color="dimmed">No share created yet</Text>
<Accordion>
<Accordion.Item
value="customization"
sx={{ borderBottom: "none" }}
>
<Accordion.Control p={0}>
<Text size="sm">
{`${reverseShare.shares.length} share${
reverseShare.shares.length > 1 ? "s" : ""
}`}
</Text>
</Accordion.Control>
<Accordion.Panel>
{reverseShare.shares.map((share) => (
<Group key={share.id} mb={4}>
<Text maw={120} truncate>
{share.id}
</Text>
<ActionIcon
color="victoria"
variant="light"
size={25}
onClick={() => {
if (window.isSecureContext) {
clipboard.copy(
`${config.get("APP_URL")}/share/${
share.id
}`
);
toast.success(
"The share link was copied to the keyboard."
);
} else {
showShareLinkModal(
modals,
share.id,
config.get("APP_URL")
);
}
}}
>
<TbLink />
</ActionIcon>
</Group>
))}
</Accordion.Panel>
</Accordion.Item>
</Accordion>
)}
</td>
<td>{reverseShare.share?.views ?? "0"}</td>
<td>{reverseShare.remainingUses}</td>
<td>
{byteToHumanSizeString(parseInt(reverseShare.maxShareSize))}
</td>
@@ -115,33 +165,6 @@ const MyShares = () => {
</td>
<td>
<Group position="right">
{reverseShare.share && (
<ActionIcon
color="victoria"
variant="light"
size={25}
onClick={() => {
if (window.isSecureContext) {
clipboard.copy(
`${config.get("APP_URL")}/share/${
reverseShare.share!.id
}`
);
toast.success(
"The share link was copied to the keyboard."
);
} else {
showShareLinkModal(
modals,
reverseShare.share!.id,
config.get("APP_URL")
);
}
}}
>
<TbLink />
</ActionIcon>
)}
<ActionIcon
color="red"
variant="light"
@@ -152,13 +175,14 @@ const MyShares = () => {
children: (
<Text size="sm">
Do you really want to delete this reverse share?
If you do, the share will be deleted as well.
If you do, the associated shares will be deleted
as well.
</Text>
),
confirmProps: {
color: "red",
},
labels: { confirm: "Confirm", cancel: "Cancel" },
labels: { confirm: "Delete", cancel: "Cancel" },
onConfirm: () => {
shareService.removeReverseShare(reverseShare.id);
setReverseShares(

View File

@@ -99,12 +99,14 @@ const uploadFile = async (
const createReverseShare = async (
shareExpiration: string,
maxShareSize: number,
maxUseCount: number,
sendEmailNotification: boolean
) => {
return (
await api.post("reverseShares", {
shareExpiration,
maxShareSize: maxShareSize.toString(),
maxUseCount,
sendEmailNotification,
})
).data;

View File

@@ -31,7 +31,8 @@ export type MyReverseShare = {
id: string;
maxShareSize: string;
shareExpiration: Date;
share?: MyShare;
remainingUses: number;
shares: MyShare[];
};
export type ShareSecurity = {