Skip to content

Commit 3f57740

Browse files
committed
Web: Add sharing
1 parent b99ee24 commit 3f57740

File tree

12 files changed

+151
-94
lines changed

12 files changed

+151
-94
lines changed

web/components/interface/extension-suggestion-overlay.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export default function ExtensionSuggestionOverlay() {
1818
visibility: "public",
1919
},
2020
isEnabled: false,
21-
remoteOrigin: "https://cdn.pulse-editor.com/extension",
21+
remoteOrigin: `${process.env.NEXT_PUBLIC_CDN_URL}/${process.env.NEXT_PUBLIC_STORAGE_CONTAINER}`,
2222
}}
2323
/>
2424
<ExtensionPreview
@@ -32,7 +32,7 @@ export default function ExtensionSuggestionOverlay() {
3232
visibility: "public",
3333
},
3434
isEnabled: false,
35-
remoteOrigin: "https://cdn.pulse-editor.com/extension",
35+
remoteOrigin: `${process.env.NEXT_PUBLIC_CDN_URL}/${process.env.NEXT_PUBLIC_STORAGE_CONTAINER}`,
3636
}}
3737
/>
3838
<ExtensionPreview
@@ -46,7 +46,7 @@ export default function ExtensionSuggestionOverlay() {
4646
visibility: "public",
4747
},
4848
isEnabled: false,
49-
remoteOrigin: "https://cdn.pulse-editor.com/extension",
49+
remoteOrigin: `${process.env.NEXT_PUBLIC_CDN_URL}/${process.env.NEXT_PUBLIC_STORAGE_CONTAINER}`,
5050
}}
5151
/>
5252
</div>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { ReactNode } from "react";
2+
3+
export default function NotAuthorized({ children }: { children?: ReactNode }) {
4+
return (
5+
<div className="flex h-full w-full flex-col items-center justify-center gap-y-4">
6+
<p className="text-danger text-4xl font-bold">Not Authorized🚫</p>
7+
<p className="text-foreground text-center text-lg">
8+
You do not have permission to access this content.
9+
</p>
10+
{children}
11+
</div>
12+
);
13+
}

web/components/modals/extension-marketplace-modal.tsx

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import ModalWrapper from "./modal-wrapper";
2-
import { Extension, TabItem } from "@/lib/types";
2+
import { Extension, ExtensionMeta, TabItem } from "@/lib/types";
33
import { useContext, useEffect, useState } from "react";
44
import Tabs from "../misc/tabs";
55
import { EditorContext } from "../providers/editor-context-provider";
@@ -43,24 +43,12 @@ export default function ExtensionMarketplaceModal({
4343
isLoading: isLoadingMarketplaceExtensions,
4444
mutate: mutateMarketplaceExtensions,
4545
} = useSWR<Extension[]>(
46-
isOpen ? "https://pulse-editor.com/api/extension/list" : null,
46+
isOpen ? `${process.env.NEXT_PUBLIC_BACKEND_URL}/api/extension/list` : null,
4747
(url: string) =>
4848
fetch(url)
4949
.then((res) => res.json())
5050
.then((body) => {
51-
const fetchedExts: {
52-
name: string;
53-
version: string;
54-
description?: string;
55-
displayName?: string;
56-
user: {
57-
name: string;
58-
};
59-
org: {
60-
name: string;
61-
};
62-
visibility: string;
63-
}[] = body;
51+
const fetchedExts: ExtensionMeta[] = body;
6452
const extensions: Extension[] = fetchedExts.map((ext) => {
6553
return {
6654
config: {
@@ -72,7 +60,7 @@ export default function ExtensionMarketplaceModal({
7260
visibility: ext.visibility,
7361
},
7462
isEnabled: true,
75-
remoteOrigin: `https://cdn.pulse-editor.com/extension`,
63+
remoteOrigin: `${process.env.NEXT_PUBLIC_CDN_URL}/${process.env.NEXT_PUBLIC_STORAGE_CONTAINER}`,
7664
};
7765
});
7866
return extensions;

web/components/modals/sharing-modal.tsx

Lines changed: 64 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import { Button, Select, SelectItem } from "@heroui/react";
1+
import { Button, Input, Select, SelectItem } from "@heroui/react";
22
import ModalWrapper from "./modal-wrapper";
33
import { useSearchParams } from "next/navigation";
44
import QRDisplay from "../misc/qr-display";
55
import Tabs from "../misc/tabs";
66
import { TabItem } from "@/lib/types";
7-
import { useEffect, useState } from "react";
7+
import { useState } from "react";
88
import Icon from "../misc/icon";
99
import toast from "react-hot-toast";
10-
import { useAuth } from "@/lib/hooks/use-auth";
10+
import useSWR from "swr";
1111

1212
export default function SharingModal({
1313
isOpen,
@@ -36,35 +36,57 @@ export default function SharingModal({
3636
);
3737

3838
const visibilityOptions = ["public", "unlisted", "private"];
39-
const [selectedVisibility, setSelectedVisibility] = useState<
40-
string | undefined
41-
>(undefined);
42-
43-
const { session } = useAuth();
44-
const [isDeveloper, setIsDeveloper] = useState(false);
45-
46-
// Load app visibility
47-
useEffect(() => {
48-
async function getVisibility() {
49-
if (!app) return;
50-
51-
const url = new URL(`https://pulse-editor.com/api/extension/get`);
52-
url.searchParams.append("app", app);
53-
url.searchParams.append("latest", "true");
5439

40+
const {
41+
data: shareInfo,
42+
mutate,
43+
isLoading: isLoadingShareInfo,
44+
} = useSWR<
45+
| {
46+
visibility: string;
47+
canEdit: boolean;
48+
inviteCode?: string;
49+
}
50+
| undefined
51+
>(
52+
app
53+
? `${process.env.NEXT_PUBLIC_BACKEND_URL}/api/extension/get-share-info?name=${app}`
54+
: null,
55+
async (url: URL) => {
5556
const res = await fetch(url, {
5657
credentials: "include",
5758
});
5859

60+
if (!res.ok) {
61+
toast.error("Failed to fetch extension share info");
62+
return undefined;
63+
}
64+
5965
const data: {
6066
visibility: string;
61-
}[] = await res.json();
62-
if (data) {
63-
const ext = data[0];
64-
setSelectedVisibility(ext.visibility);
65-
}
66-
}
67-
}, []);
67+
canEdit: boolean;
68+
inviteCode?: string;
69+
} = await res.json();
70+
71+
return data;
72+
},
73+
);
74+
75+
async function updateShareInfo(visibility: string) {
76+
const url = new URL(
77+
`${process.env.NEXT_PUBLIC_BACKEND_URL}/api/extension/update`,
78+
);
79+
await fetch(url, {
80+
method: "PATCH",
81+
body: JSON.stringify({
82+
visibility,
83+
name: app,
84+
}),
85+
credentials: "include",
86+
});
87+
88+
mutate();
89+
}
6890

6991
return (
7092
<ModalWrapper
@@ -79,19 +101,25 @@ export default function SharingModal({
79101
label="Visibility"
80102
placeholder="Select visibility"
81103
onChange={(e) => {
82-
setSelectedVisibility(e.target.value);
104+
updateShareInfo(e.target.value);
83105
}}
84-
isDisabled={!isDeveloper}
106+
isDisabled={!shareInfo?.canEdit}
107+
isLoading={isLoadingShareInfo}
108+
selectedKeys={shareInfo?.visibility ? [shareInfo.visibility] : []}
85109
>
86110
{visibilityOptions.map((option) => (
87111
<SelectItem key={option}>{option}</SelectItem>
88112
))}
89113
</Select>
90114
)}
91115

92-
{!selectedVisibility && app ? (
116+
{app && shareInfo?.canEdit && shareInfo.visibility === "unlisted" && (
117+
<Input value={shareInfo.inviteCode} label="Invite Code" readOnly />
118+
)}
119+
120+
{!shareInfo?.visibility && app ? (
93121
<p>Select a visibility option to see sharing options.</p>
94-
) : selectedVisibility === "private" && app ? (
122+
) : shareInfo?.visibility === "private" && app ? (
95123
<p>Your workspace is private.</p>
96124
) : (
97125
<>
@@ -106,7 +134,9 @@ export default function SharingModal({
106134
<p className="text-content4-foreground text-sm">
107135
Share your workspace via this QR Code
108136
</p>
109-
<QRDisplay url={window.location.href} />
137+
<QRDisplay
138+
url={`${window.location.origin}?app=${app}${shareInfo?.inviteCode ? `&inviteCode=${shareInfo.inviteCode}` : ""}`}
139+
/>
110140
</div>
111141
)}
112142

@@ -116,11 +146,13 @@ export default function SharingModal({
116146
Share your workspace via this URL
117147
</p>
118148

119-
<p className="font-bold break-all">{window.location.href}</p>
149+
<p className="font-bold break-all">{`${window.location.origin}?app=${app}${shareInfo?.inviteCode ? `&inviteCode=${shareInfo.inviteCode}` : ""}`}</p>
120150
<Button
121151
color="primary"
122152
onPress={() => {
123-
navigator.clipboard.writeText(window.location.href);
153+
navigator.clipboard.writeText(
154+
`${window.location.origin}?app=${app}${shareInfo?.inviteCode ? `&inviteCode=${shareInfo.inviteCode}` : ""}`,
155+
);
124156
toast.success("URL copied to clipboard");
125157
}}
126158
>

web/components/views/file-view-display-area.tsx

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ import { useViewManager } from "@/lib/hooks/use-view-manager";
33
import ExtensionViewLayout from "./layout";
44
import ViewLoader from "./loaders/view-loader";
55
import useExtensionManager from "@/lib/hooks/use-extension-manager";
6-
import { Extension } from "@/lib/types";
6+
import { Extension, ExtensionMeta } from "@/lib/types";
77
import { useSearchParams } from "next/navigation";
88
import { useEffect, useState } from "react";
99
import { compare } from "semver";
1010
import { ViewModel } from "@pulse-editor/shared-utils";
11+
import NotAuthorized from "../interface/not-authorized";
1112

1213
export default function ViewDisplayArea() {
1314
const { updateViewModel, activeViewModel } = useViewManager();
@@ -16,37 +17,34 @@ export default function ViewDisplayArea() {
1617
const params = useSearchParams();
1718
// Use the 'app' query parameter to load specific extension app upon loading page
1819
const app = params.get("app");
20+
const inviteCode = params.get("inviteCode");
1921

2022
const { installExtension } = useExtensionManager();
2123
const [pulseAppViewModel, setPulseAppViewModel] = useState<
2224
ViewModel | undefined
2325
>(undefined);
26+
const [noAccessToApp, setNoAccessToApp] = useState<boolean>(false);
2427

2528
useEffect(() => {
2629
// Download and load the extension app if specified
27-
async function loadApp(appName: string) {
28-
const url = new URL(`https://pulse-editor.com/api/extension/get`);
30+
async function loadApp(appName: string, inviteCode?: string) {
31+
const url = new URL(
32+
`${process.env.NEXT_PUBLIC_BACKEND_URL}/api/extension/get`,
33+
);
2934
url.searchParams.set("name", appName);
35+
url.searchParams.set("latest", "true");
36+
if (inviteCode) url.searchParams.set("inviteCode", inviteCode);
3037

31-
const res = await fetch(url);
38+
const res = await fetch(url, {
39+
credentials: "include",
40+
});
3241

3342
if (!res.ok) {
34-
throw new Error("Failed to fetch extension app");
43+
setNoAccessToApp(true);
44+
return;
3545
}
3646

37-
const fetchedExts: {
38-
name: string;
39-
version: string;
40-
description?: string;
41-
displayName?: string;
42-
user: {
43-
name: string;
44-
};
45-
org: {
46-
name: string;
47-
};
48-
visibility: string;
49-
}[] = await res.json();
47+
const fetchedExts: ExtensionMeta[] = await res.json();
5048

5149
console.log("Fetched extensions:", fetchedExts);
5250

@@ -61,7 +59,7 @@ export default function ViewDisplayArea() {
6159
visibility: ext.visibility,
6260
},
6361
isEnabled: true,
64-
remoteOrigin: `https://cdn.pulse-editor.com/extension`,
62+
remoteOrigin: `${process.env.NEXT_PUBLIC_CDN_URL}/${process.env.NEXT_PUBLIC_STORAGE_CONTAINER}`,
6563
};
6664
});
6765

@@ -70,6 +68,11 @@ export default function ViewDisplayArea() {
7068
return compare(b.config.version, a.config.version);
7169
})[0];
7270

71+
if (!ext) {
72+
setNoAccessToApp(true);
73+
return;
74+
}
75+
7376
console.log("Installing extension:", ext);
7477

7578
await installExtension(ext);
@@ -82,12 +85,16 @@ export default function ViewDisplayArea() {
8285
}
8386

8487
if (app) {
85-
loadApp(app);
88+
loadApp(app, inviteCode ?? undefined);
8689
}
8790
}, [app]);
8891

8992
// #endregion
9093

94+
if (noAccessToApp) {
95+
return <NotAuthorized />;
96+
}
97+
9198
return (
9299
<div className="flex h-full w-full flex-col p-1">
93100
<div className="bg-default flex h-full w-full flex-col items-start justify-between gap-1.5 overflow-hidden rounded-xl p-2">

web/lib/hooks/use-auth.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
1+
"use client";
2+
13
import { useContext } from "react";
24
import { Session } from "../types";
35
import useSWR from "swr";
46
import { EditorContext } from "@/components/providers/editor-context-provider";
57

6-
const authUrl = "https://pulse-editor.com";
7-
// const authUrl = "https://localhost:8080";
8-
98
export function useAuth() {
109
// --- Auth ---
1110
const { data: session, isLoading } = useSWR<Session | undefined>(
12-
`${authUrl}/api/auth/session`,
11+
`${process.env.NEXT_PUBLIC_BACKEND_URL}/api/auth/session`,
1312
async (url: string) => {
1413
const res = await fetch(url, {
1514
credentials: "include",
@@ -34,7 +33,9 @@ export function useAuth() {
3433
return;
3534
}
3635

37-
const url = new URL(`${authUrl}/api/auth/signin`);
36+
const url = new URL(
37+
`${process.env.NEXT_PUBLIC_BACKEND_URL}/api/auth/signin`,
38+
);
3839
url.searchParams.set("callbackUrl", window.location.href);
3940

4041
window.location.href = url.toString();
@@ -46,7 +47,9 @@ export function useAuth() {
4647
return;
4748
}
4849

49-
const url = new URL(`${authUrl}/api/auth/signout`);
50+
const url = new URL(
51+
`${process.env.NEXT_PUBLIC_BACKEND_URL}/api/auth/signout`,
52+
);
5053
url.searchParams.set("callbackUrl", window.location.href);
5154
window.location.href = url.toString();
5255
}

0 commit comments

Comments
 (0)