unsharing logic
This commit is contained in:
parent
5ba1c03030
commit
e23b38875e
@ -5,7 +5,7 @@ import { ZodError, z } from "zod";
|
|||||||
|
|
||||||
import { user, sandbox, usersToSandboxes } from "./schema";
|
import { user, sandbox, usersToSandboxes } from "./schema";
|
||||||
import * as schema from "./schema";
|
import * as schema from "./schema";
|
||||||
import { eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
|
|
||||||
export interface Env {
|
export interface Env {
|
||||||
DB: D1Database;
|
DB: D1Database;
|
||||||
@ -91,33 +91,48 @@ export default {
|
|||||||
console.log(method);
|
console.log(method);
|
||||||
return methodNotAllowed;
|
return methodNotAllowed;
|
||||||
}
|
}
|
||||||
} else if (path === "/api/sandbox/share" && method === "POST") {
|
} else if (path === "/api/sandbox/share") {
|
||||||
const shareSchema = z.object({
|
if (method === "POST") {
|
||||||
sandboxId: z.string(),
|
const shareSchema = z.object({
|
||||||
email: z.string(),
|
sandboxId: z.string(),
|
||||||
});
|
email: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
const { sandboxId, email } = shareSchema.parse(body);
|
const { sandboxId, email } = shareSchema.parse(body);
|
||||||
|
|
||||||
const user = await db.query.user.findFirst({
|
const user = await db.query.user.findFirst({
|
||||||
where: (user, { eq }) => eq(user.email, email),
|
where: (user, { eq }) => eq(user.email, email),
|
||||||
with: {
|
with: {
|
||||||
usersToSandboxes: true,
|
usersToSandboxes: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return new Response("No user associated with email.", { status: 400 });
|
return new Response("No user associated with email.", { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.usersToSandboxes.find((uts) => uts.sandboxId === sandboxId)) {
|
if (user.usersToSandboxes.find((uts) => uts.sandboxId === sandboxId)) {
|
||||||
return new Response("User already has access.", { status: 400 });
|
return new Response("User already has access.", { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.insert(usersToSandboxes).values({ userId: user.id, sandboxId }).get();
|
await db.insert(usersToSandboxes).values({ userId: user.id, sandboxId }).get();
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
|
} else if (method === "DELETE") {
|
||||||
|
const deleteShareSchema = z.object({
|
||||||
|
sandboxId: z.string(),
|
||||||
|
userId: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const body = await request.json();
|
||||||
|
const { sandboxId, userId } = deleteShareSchema.parse(body);
|
||||||
|
console.log("DELETE", sandboxId, userId);
|
||||||
|
|
||||||
|
await db.delete(usersToSandboxes).where(and(eq(usersToSandboxes.userId, userId), eq(usersToSandboxes.sandboxId, sandboxId)));
|
||||||
|
|
||||||
|
return success;
|
||||||
|
} else return methodNotAllowed;
|
||||||
} else if (path === "/api/user") {
|
} else if (path === "/api/user") {
|
||||||
if (method === "GET") {
|
if (method === "GET") {
|
||||||
const params = url.searchParams;
|
const params = url.searchParams;
|
||||||
|
@ -3,10 +3,8 @@
|
|||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogDescription,
|
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
|
||||||
} from "@/components/ui/dialog"
|
} from "@/components/ui/dialog"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
@ -15,27 +13,19 @@ import { useForm } from "react-hook-form"
|
|||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
FormDescription,
|
|
||||||
FormField,
|
FormField,
|
||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form"
|
} from "@/components/ui/form"
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@/components/ui/select"
|
|
||||||
import { Loader2, UserPlus, X } from "lucide-react"
|
import { Loader2, UserPlus, X } from "lucide-react"
|
||||||
import { useEffect, useState, useTransition } from "react"
|
import { useState } from "react"
|
||||||
import { Sandbox, User } from "@/lib/types"
|
import { Sandbox } from "@/lib/types"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import Avatar from "@/components/ui/avatar"
|
|
||||||
import { shareSandbox } from "@/lib/actions"
|
import { shareSandbox } from "@/lib/actions"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
|
import SharedUser from "./sharedUser"
|
||||||
|
import { DialogDescription } from "@radix-ui/react-dialog"
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
email: z.string().email(),
|
email: z.string().email(),
|
||||||
@ -79,9 +69,15 @@ export default function ShareSandboxModal({
|
|||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
<DialogContent className="p-0">
|
<DialogContent className="p-0">
|
||||||
<div className="p-6 pb-3 space-y-6">
|
<div className={`p-6 ${shared.length > 0 ? "pb-3" : null} space-y-6`}>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Share Sandbox</DialogTitle>
|
<DialogTitle>Share Sandbox</DialogTitle>
|
||||||
|
{data.visibility === "private" ? (
|
||||||
|
<DialogDescription className="text-sm text-muted-foreground">
|
||||||
|
This sandbox is private. Making it public will allow shared
|
||||||
|
users to view and collaborate.
|
||||||
|
</DialogDescription>
|
||||||
|
) : null}
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)} className="flex">
|
<form onSubmit={form.handleSubmit(onSubmit)} className="flex">
|
||||||
@ -115,25 +111,21 @@ export default function ShareSandboxModal({
|
|||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full h-[1px] bg-border" />
|
{shared.length > 0 ? (
|
||||||
<div className="p-6 pt-3">
|
<>
|
||||||
<DialogHeader className="mb-6">
|
<div className="w-full h-[1px] mb- bg-border" />
|
||||||
<DialogTitle>Manage Access</DialogTitle>
|
<div className="p-6 pt-3">
|
||||||
</DialogHeader>
|
<DialogHeader className="mb-6">
|
||||||
<div className="space-y-2">
|
<DialogTitle>Manage Access</DialogTitle>
|
||||||
{shared.map((user) => (
|
</DialogHeader>
|
||||||
<div key={user.id} className="flex items-center justify-between">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center">
|
{shared.map((user) => (
|
||||||
<Avatar name={user.name} className="mr-2" />
|
<SharedUser key={user.id} user={user} sandboxId={data.id} />
|
||||||
{user.name}
|
))}
|
||||||
</div>
|
|
||||||
<Button variant="ghost" size="smIcon">
|
|
||||||
<X className="w-4 h-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
</div>
|
||||||
</div>
|
</>
|
||||||
</div>
|
) : null}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
|
44
frontend/components/editor/navbar/sharedUser.tsx
Normal file
44
frontend/components/editor/navbar/sharedUser.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import Avatar from "@/components/ui/avatar"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { unshareSandbox } from "@/lib/actions"
|
||||||
|
import { Loader2, X } from "lucide-react"
|
||||||
|
import { useState } from "react"
|
||||||
|
|
||||||
|
export default function SharedUser({
|
||||||
|
user,
|
||||||
|
sandboxId,
|
||||||
|
}: {
|
||||||
|
user: { id: string; name: string }
|
||||||
|
sandboxId: string
|
||||||
|
}) {
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
|
async function handleUnshare(id: string) {
|
||||||
|
setLoading(true)
|
||||||
|
|
||||||
|
await unshareSandbox(sandboxId, user.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Avatar name={user.name} className="mr-2" />
|
||||||
|
{user.name}
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
disabled={loading}
|
||||||
|
onClick={() => handleUnshare(user.id)}
|
||||||
|
variant="ghost"
|
||||||
|
size="smIcon"
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<Loader2 className="animate-spin w-4 h-4" />
|
||||||
|
) : (
|
||||||
|
<X className="w-4 h-4" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -60,3 +60,15 @@ export async function shareSandbox(sandboxId: string, email: string) {
|
|||||||
revalidatePath(`/code/${sandboxId}`)
|
revalidatePath(`/code/${sandboxId}`)
|
||||||
return { success: true, message: "Shared successfully." }
|
return { success: true, message: "Shared successfully." }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function unshareSandbox(sandboxId: string, userId: string) {
|
||||||
|
const res = await fetch("http://localhost:8787/api/sandbox/share", {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ sandboxId, userId }),
|
||||||
|
})
|
||||||
|
|
||||||
|
revalidatePath(`/code/${sandboxId}`)
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user