enzostvs HF Staff commited on
Commit
dfcc023
·
1 Parent(s): 86a6b9a

handle private project

Browse files
app/api/me/projects/[namespace]/[repoId]/route.ts CHANGED
@@ -169,6 +169,7 @@ export async function GET(
169
  project: {
170
  id: space.id,
171
  space_id: space.name,
 
172
  _updatedAt: space.updatedAt,
173
  },
174
  pages: htmlFiles,
 
169
  project: {
170
  id: space.id,
171
  space_id: space.name,
172
+ private: space.private,
173
  _updatedAt: space.updatedAt,
174
  },
175
  pages: htmlFiles,
app/api/me/route.ts CHANGED
@@ -31,7 +31,6 @@ export async function GET() {
31
  }
32
  })) {
33
  if (
34
- !space.private &&
35
  space.sdk === "static" &&
36
  Array.isArray((space.cardData as { tags?: string[] })?.tags) &&
37
  (
 
31
  }
32
  })) {
33
  if (
 
34
  space.sdk === "static" &&
35
  Array.isArray((space.cardData as { tags?: string[] })?.tags) &&
36
  (
components/editor/header/index.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import { ArrowRight, HelpCircle, RefreshCcw } from "lucide-react";
2
  import Image from "next/image";
3
  import Link from "next/link";
4
 
@@ -10,9 +10,17 @@ import { UserMenu } from "@/components/user-menu";
10
  import { SwitchDevice } from "@/components/editor/switch-devide";
11
  import { SwitchTab } from "./switch-tab";
12
  import { History } from "@/components/editor/history";
 
 
 
 
 
 
13
 
14
  export function Header() {
 
15
  const { user, openLoginWindow } = useUser();
 
16
  return (
17
  <header className="border-b bg-neutral-950 dark:border-neutral-800 grid grid-cols-3 lg:flex items-center max-lg:gap-3 justify-between z-20">
18
  <div className="flex items-center justify-between lg:max-w-[600px] lg:w-full py-2 px-2 lg:px-3 lg:pl-6 gap-3">
@@ -72,14 +80,33 @@ export function Header() {
72
  </Button>
73
  </Link>
74
  </div>
75
- {user ? (
76
- <UserMenu className="!pl-1 !pr-3 !py-1 !h-auto" />
77
- ) : (
78
- <Button size="sm" onClick={openLoginWindow}>
79
- Start Vibe Coding
80
- <ArrowRight className="size-4" />
81
- </Button>
82
- )}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  </div>
84
  </header>
85
  );
 
1
+ import { ArrowRight, HelpCircle, RefreshCcw, Lock } from "lucide-react";
2
  import Image from "next/image";
3
  import Link from "next/link";
4
 
 
10
  import { SwitchDevice } from "@/components/editor/switch-devide";
11
  import { SwitchTab } from "./switch-tab";
12
  import { History } from "@/components/editor/history";
13
+ import { useEditor } from "@/hooks/useEditor";
14
+ import {
15
+ Tooltip,
16
+ TooltipContent,
17
+ TooltipTrigger,
18
+ } from "@/components/ui/tooltip";
19
 
20
  export function Header() {
21
+ const { project } = useEditor();
22
  const { user, openLoginWindow } = useUser();
23
+ console.log(project);
24
  return (
25
  <header className="border-b bg-neutral-950 dark:border-neutral-800 grid grid-cols-3 lg:flex items-center max-lg:gap-3 justify-between z-20">
26
  <div className="flex items-center justify-between lg:max-w-[600px] lg:w-full py-2 px-2 lg:px-3 lg:pl-6 gap-3">
 
80
  </Button>
81
  </Link>
82
  </div>
83
+ <div className="flex items-center gap-2">
84
+ {project?.private && (
85
+ <Tooltip>
86
+ <TooltipTrigger>
87
+ <div className="max-lg:hidden flex items-center gap-1.5 bg-amber-500/10 backdrop-blur-sm px-3 py-1.5 rounded-full border border-amber-500/20 shadow-lg">
88
+ <Lock className="w-3 h-3 text-amber-500" />
89
+ <span className="text-amber-500 text-xs font-medium tracking-wide">
90
+ Private Project
91
+ </span>
92
+ </div>
93
+ </TooltipTrigger>
94
+ <TooltipContent>
95
+ <p className="text-xs">
96
+ This project is private. Only you can see it.
97
+ </p>
98
+ </TooltipContent>
99
+ </Tooltip>
100
+ )}
101
+ {user ? (
102
+ <UserMenu className="!pl-1 !pr-3 !py-1 !h-auto" />
103
+ ) : (
104
+ <Button size="sm" onClick={openLoginWindow}>
105
+ Start Vibe Coding
106
+ <ArrowRight className="size-4" />
107
+ </Button>
108
+ )}
109
+ </div>
110
  </div>
111
  </header>
112
  );
components/editor/preview/index.tsx CHANGED
@@ -49,6 +49,116 @@ export const Preview = ({ isNew }: { isNew: boolean }) => {
49
  : "";
50
 
51
  const iframeRef = useRef<HTMLIFrameElement>(null);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  const [hoveredElement, setHoveredElement] = useState<{
53
  tagName: string;
54
  rect: { top: number; left: number; width: number; height: number };
@@ -130,7 +240,12 @@ export const Preview = ({ isNew }: { isNew: boolean }) => {
130
  if (!isEditableModeEnabled) {
131
  setHoveredElement(null);
132
  }
133
- }, [isEditableModeEnabled, project?.space_id]);
 
 
 
 
 
134
 
135
  const promoteVersion = async () => {
136
  setIsPromotingVersion(true);
@@ -196,7 +311,8 @@ export const Preview = ({ isNew }: { isNew: boolean }) => {
196
  />
197
  ) : iframeSrc === "" ||
198
  isLoadingProject ||
199
- (isAiWorking && iframeSrc == "") ? (
 
200
  <div className="w-full h-full flex items-center justify-center relative">
201
  <div className="py-10 w-full relative z-1 max-w-3xl mx-auto text-center">
202
  <AiLoading
@@ -229,7 +345,13 @@ export const Preview = ({ isNew }: { isNew: boolean }) => {
229
  device === "mobile",
230
  }
231
  )}
232
- src={iframeSrc}
 
 
 
 
 
 
233
  allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; clipboard-read; clipboard-write; display-capture; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; serial; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking"
234
  />
235
  <div
 
49
  : "";
50
 
51
  const iframeRef = useRef<HTMLIFrameElement>(null);
52
+
53
+ // For private projects, use srcDoc instead of proxy URL
54
+ const shouldUseCustomIframe = project?.private && currentPageData?.html;
55
+
56
+ // Inject event handling script for private projects
57
+ const injectInteractivityScript = (html: string) => {
58
+ const interactivityScript = `
59
+ <script>
60
+ // Add event listeners and communicate with parent
61
+ document.addEventListener('DOMContentLoaded', function() {
62
+ let hoveredElement = null;
63
+ let isEditModeEnabled = false;
64
+
65
+ document.addEventListener('mouseover', function(event) {
66
+ if (event.target !== document.body && event.target !== document.documentElement) {
67
+ hoveredElement = event.target;
68
+
69
+ const rect = event.target.getBoundingClientRect();
70
+ const message = {
71
+ type: 'ELEMENT_HOVERED',
72
+ data: {
73
+ tagName: event.target.tagName,
74
+ rect: {
75
+ top: rect.top,
76
+ left: rect.left,
77
+ width: rect.width,
78
+ height: rect.height
79
+ },
80
+ element: event.target.outerHTML
81
+ }
82
+ };
83
+ parent.postMessage(message, '*');
84
+ }
85
+ });
86
+
87
+ document.addEventListener('mouseout', function(event) {
88
+ hoveredElement = null;
89
+
90
+ parent.postMessage({
91
+ type: 'ELEMENT_MOUSE_OUT'
92
+ }, '*');
93
+ });
94
+
95
+ // Handle clicks - prevent default only in edit mode
96
+ document.addEventListener('click', function(event) {
97
+ if (isEditModeEnabled) {
98
+ event.preventDefault();
99
+ event.stopPropagation();
100
+
101
+ const rect = event.target.getBoundingClientRect();
102
+ parent.postMessage({
103
+ type: 'ELEMENT_CLICKED',
104
+ data: {
105
+ tagName: event.target.tagName,
106
+ rect: {
107
+ top: rect.top,
108
+ left: rect.left,
109
+ width: rect.width,
110
+ height: rect.height
111
+ },
112
+ element: event.target.outerHTML
113
+ }
114
+ }, '*');
115
+ }
116
+ });
117
+
118
+ // Prevent form submissions when in edit mode
119
+ document.addEventListener('submit', function(event) {
120
+ if (isEditModeEnabled) {
121
+ event.preventDefault();
122
+ event.stopPropagation();
123
+ }
124
+ });
125
+
126
+ // Prevent other navigation events when in edit mode
127
+ document.addEventListener('keydown', function(event) {
128
+ if (isEditModeEnabled && event.key === 'Enter' && (event.target.tagName === 'A' || event.target.tagName === 'BUTTON')) {
129
+ event.preventDefault();
130
+ event.stopPropagation();
131
+ }
132
+ });
133
+
134
+ // Listen for messages from parent
135
+ window.addEventListener('message', function(event) {
136
+ if (event.data.type === 'ENABLE_EDIT_MODE') {
137
+ isEditModeEnabled = true;
138
+ document.body.style.userSelect = 'none';
139
+ document.body.style.pointerEvents = 'auto';
140
+ } else if (event.data.type === 'DISABLE_EDIT_MODE') {
141
+ isEditModeEnabled = false;
142
+ document.body.style.userSelect = '';
143
+ document.body.style.pointerEvents = '';
144
+ }
145
+ });
146
+
147
+ // Notify parent that script is ready
148
+ parent.postMessage({
149
+ type: 'PROXY_SCRIPT_READY'
150
+ }, '*');
151
+ });
152
+ </script>
153
+ `;
154
+
155
+ // Inject the script before closing body tag, or at the end if no body tag
156
+ if (html.includes("</body>")) {
157
+ return html.replace("</body>", `${interactivityScript}</body>`);
158
+ } else {
159
+ return html + interactivityScript;
160
+ }
161
+ };
162
  const [hoveredElement, setHoveredElement] = useState<{
163
  tagName: string;
164
  rect: { top: number; left: number; width: number; height: number };
 
240
  if (!isEditableModeEnabled) {
241
  setHoveredElement(null);
242
  }
243
+ }, [
244
+ isEditableModeEnabled,
245
+ project?.space_id,
246
+ shouldUseCustomIframe,
247
+ currentPageData?.html,
248
+ ]);
249
 
250
  const promoteVersion = async () => {
251
  setIsPromotingVersion(true);
 
311
  />
312
  ) : iframeSrc === "" ||
313
  isLoadingProject ||
314
+ (isAiWorking && iframeSrc == "") ||
315
+ (shouldUseCustomIframe && !currentPageData?.html) ? (
316
  <div className="w-full h-full flex items-center justify-center relative">
317
  <div className="py-10 w-full relative z-1 max-w-3xl mx-auto text-center">
318
  <AiLoading
 
345
  device === "mobile",
346
  }
347
  )}
348
+ {...(shouldUseCustomIframe
349
+ ? {
350
+ srcDoc: injectInteractivityScript(
351
+ currentPageData?.html || ""
352
+ ),
353
+ }
354
+ : { src: iframeSrc })}
355
  allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; clipboard-read; clipboard-write; display-capture; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; serial; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking"
356
  />
357
  <div
components/my-projects/project-card.tsx CHANGED
@@ -1,6 +1,6 @@
1
  import Link from "next/link";
2
  import { formatDistance } from "date-fns";
3
- import { EllipsisVertical, Settings, Trash } from "lucide-react";
4
 
5
  import { Button } from "@/components/ui/button";
6
  import {
@@ -37,6 +37,14 @@ export function ProjectCard({
37
  onDelete();
38
  }
39
  };
 
 
 
 
 
 
 
 
40
 
41
  return (
42
  <div className="text-neutral-200 space-y-4 group cursor-pointer">
@@ -44,16 +52,30 @@ export function ProjectCard({
44
  href={`/projects/${project.name}`}
45
  className="relative bg-neutral-900 rounded-2xl overflow-hidden h-64 lg:h-44 w-full flex items-center justify-end flex-col px-3 border border-neutral-800"
46
  >
47
- <div className="absolute inset-0 w-full h-full overflow-hidden">
48
- <iframe
49
- src={`/api/proxy/?spaceId=${encodeURIComponent(project.name)}`}
50
- className="w-[1200px] h-[675px] border-0 origin-top-left"
51
- style={{
52
- transform: "scale(0.5)",
53
- transformOrigin: "top left",
54
- }}
55
- />
56
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
  <Button
59
  variant="default"
 
1
  import Link from "next/link";
2
  import { formatDistance } from "date-fns";
3
+ import { EllipsisVertical, Lock, Settings, Trash } from "lucide-react";
4
 
5
  import { Button } from "@/components/ui/button";
6
  import {
 
37
  onDelete();
38
  }
39
  };
40
+ // from-gray-600 to-gray-600
41
+ // from-blue-600 to-blue-600
42
+ // from-green-600 to-green-600
43
+ // from-yellow-600 to-yellow-600
44
+ // from-purple-600 to-purple-600
45
+ // from-pink-600 to-pink-600
46
+ // from-red-600 to-red-600
47
+ // from-orange-600 to-orange-600
48
 
49
  return (
50
  <div className="text-neutral-200 space-y-4 group cursor-pointer">
 
52
  href={`/projects/${project.name}`}
53
  className="relative bg-neutral-900 rounded-2xl overflow-hidden h-64 lg:h-44 w-full flex items-center justify-end flex-col px-3 border border-neutral-800"
54
  >
55
+ {project.private ? (
56
+ <div
57
+ className={`absolute inset-0 w-full h-full flex items-center justify-center bg-gradient-to-br from-${project.cardData?.colorFrom}-600 to-${project.cardData?.colorTo}-600`}
58
+ >
59
+ <div className="absolute top-3 right-3 flex items-center gap-1.5 bg-black/60 backdrop-blur-sm px-3 py-1.5 rounded-full border border-white/10 shadow-lg">
60
+ <Lock className="w-3 h-3 text-white/90" />
61
+ <span className="text-white/90 text-xs font-medium tracking-wide">
62
+ Private
63
+ </span>
64
+ </div>
65
+ <p className="text-4xl">{project.cardData?.emoji}</p>
66
+ </div>
67
+ ) : (
68
+ <div className="absolute inset-0 w-full h-full overflow-hidden">
69
+ <iframe
70
+ src={`/api/proxy/?spaceId=${encodeURIComponent(project.name)}`}
71
+ className="w-[1200px] h-[675px] border-0 origin-top-left"
72
+ style={{
73
+ transform: "scale(0.5)",
74
+ transformOrigin: "top left",
75
+ }}
76
+ />
77
+ </div>
78
+ )}
79
 
80
  <Button
81
  variant="default"