abdullahalivv commited on
Commit
ce9a70b
·
verified ·
1 Parent(s): f232505

Upload 2 files

Browse files
src/components/deploy-button/deploy-button.tsx ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { useState } from "react";
3
+ import classNames from "classnames";
4
+ import { toast } from "react-toastify";
5
+ import { FaPowerOff } from "react-icons/fa6";
6
+
7
+ import SpaceIcon from "@/assets/space.svg";
8
+ import Loading from "../loading/loading";
9
+ import Login from "../login/login";
10
+ import { Auth } from "./../../../utils/types";
11
+
12
+ const MsgToast = ({ url }: { url: string }) => (
13
+ <div className="w-full flex items-center justify-center gap-3">
14
+ Your space is live!
15
+ <button
16
+ className="bg-black text-sm block text-white rounded-md px-3 py-1.5 hover:bg-gray-900 cursor-pointer"
17
+ onClick={() => {
18
+ window.open(url, "_blank");
19
+ }}
20
+ >
21
+ See Space
22
+ </button>
23
+ </div>
24
+ );
25
+
26
+ function DeployButton({
27
+ html,
28
+ error = false,
29
+ auth,
30
+ }: {
31
+ html: string;
32
+ error: boolean;
33
+ auth?: Auth;
34
+ }) {
35
+ const [open, setOpen] = useState(false);
36
+ const [loading, setLoading] = useState(false);
37
+ const [path, setPath] = useState<string | undefined>(undefined);
38
+
39
+ const [config, setConfig] = useState({
40
+ title: "",
41
+ });
42
+
43
+ const createSpace = async () => {
44
+ setLoading(true);
45
+
46
+ try {
47
+ const request = await fetch("/api/deploy", {
48
+ method: "POST",
49
+ body: JSON.stringify({
50
+ title: config.title,
51
+ path,
52
+ html,
53
+ }),
54
+ headers: {
55
+ "Content-Type": "application/json",
56
+ },
57
+ });
58
+ const response = await request.json();
59
+ if (response.ok) {
60
+ toast.success(
61
+ <MsgToast
62
+ url={`https://huggingface.co/spaces/${response.path ?? path}`}
63
+ />,
64
+ {
65
+ autoClose: 10000,
66
+ }
67
+ );
68
+ setPath(response.path);
69
+ } else {
70
+ toast.error(response.message);
71
+ }
72
+ } catch (err: any) {
73
+ toast.error(err.message);
74
+ } finally {
75
+ setLoading(false);
76
+ setOpen(false);
77
+ }
78
+ };
79
+
80
+ return (
81
+ <div className="relative flex items-center justify-end">
82
+ {auth && (
83
+ <>
84
+ <button
85
+ className="mr-2 cursor-pointer"
86
+ onClick={() => {
87
+ if (confirm("Are you sure you want to log out?")) {
88
+ // go to /auth/logout page
89
+ window.location.href = "/auth/logout";
90
+ }
91
+ }}
92
+ >
93
+ <FaPowerOff className="text-lg text-red-500" />
94
+ </button>
95
+ <p className="mr-3 text-xs lg:text-sm text-gray-300">
96
+ <span className="max-lg:hidden">Connected as </span>
97
+ <a
98
+ href={`https://huggingface.co/${auth.preferred_username}`}
99
+ target="_blank"
100
+ className="underline hover:text-white"
101
+ >
102
+ {auth.preferred_username}
103
+ </a>
104
+ </p>
105
+ </>
106
+ )}
107
+ <button
108
+ className={classNames(
109
+ "relative cursor-pointer flex-none flex items-center justify-center rounded-md text-xs lg:text-sm font-semibold leading-5 lg:leading-6 py-1.5 px-5 hover:bg-pink-400 text-white shadow-sm dark:shadow-highlight/20",
110
+ {
111
+ "bg-pink-400": open,
112
+ "bg-pink-500": !open,
113
+ }
114
+ )}
115
+ onClick={() => setOpen(!open)}
116
+ >
117
+ {path ? "Update Space" : "Deploy to Space"}
118
+ </button>
119
+ <div
120
+ className={classNames(
121
+ "h-screen w-screen bg-black/20 fixed left-0 top-0 z-10",
122
+ {
123
+ "opacity-0 pointer-events-none": !open,
124
+ }
125
+ )}
126
+ onClick={() => setOpen(false)}
127
+ ></div>
128
+ <div
129
+ className={classNames(
130
+ "absolute top-[calc(100%+8px)] right-0 z-10 w-80 bg-white border border-gray-200 rounded-lg shadow-lg transition-all duration-75 overflow-hidden",
131
+ {
132
+ "opacity-0 pointer-events-none": !open,
133
+ }
134
+ )}
135
+ >
136
+ {!auth ? (
137
+ <Login html={html}>
138
+ <p className="text-gray-500 text-sm mb-3">
139
+ Host this project for free and share it with your friends.
140
+ </p>
141
+ </Login>
142
+ ) : (
143
+ <>
144
+ <header className="flex items-center text-sm px-4 py-2 border-b border-gray-200 gap-2 bg-gray-100 font-semibold text-gray-700">
145
+ <span className="text-xs bg-pink-500/10 text-pink-500 rounded-full pl-1.5 pr-2.5 py-0.5 flex items-center justify-start gap-1.5">
146
+ <img src={SpaceIcon} alt="Space Icon" className="size-4" />
147
+ Space
148
+ </span>
149
+ Configure Deployment
150
+ </header>
151
+ <main className="px-4 pt-3 pb-4 space-y-3">
152
+ <p className="text-xs text-amber-600 bg-amber-500/10 rounded-md p-2">
153
+ {path ? (
154
+ <span>
155
+ Your space is live at{" "}
156
+ <a
157
+ href={`https://huggingface.co/spaces/${path}`}
158
+ target="_blank"
159
+ className="underline hover:text-amber-700"
160
+ >
161
+ huggingface.co/{path}
162
+ </a>
163
+ . You can update it by deploying again.
164
+ </span>
165
+ ) : (
166
+ "Deploy your project to a space on the Hub. Spaces are a way to share your project with the world."
167
+ )}
168
+ </p>
169
+ {!path && (
170
+ <label className="block">
171
+ <p className="text-gray-600 text-sm font-medium mb-1.5">
172
+ Space Title
173
+ </p>
174
+ <input
175
+ type="text"
176
+ value={config.title}
177
+ className="mr-2 border rounded-md px-3 py-1.5 border-gray-300 w-full text-sm"
178
+ placeholder="My Awesome Space"
179
+ onChange={(e) =>
180
+ setConfig({ ...config, title: e.target.value })
181
+ }
182
+ />
183
+ </label>
184
+ )}
185
+ {error && (
186
+ <p className="text-red-500 text-xs bg-red-500/10 rounded-md p-2">
187
+ Your code has errors. Fix them before deploying.
188
+ </p>
189
+ )}
190
+ <div className="pt-2 text-right">
191
+ <button
192
+ disabled={error || loading || !config.title}
193
+ className="relative rounded-full bg-black px-5 py-2 text-white font-semibold text-xs hover:bg-black/90 transition-all duration-100 disabled:bg-gray-300 disabled:text-gray-500 disabled:cursor-not-allowed disabled:hover:bg-gray-300"
194
+ onClick={createSpace}
195
+ >
196
+ {path ? "Update Space" : "Create Space"}
197
+ {loading && <Loading />}
198
+ </button>
199
+ </div>
200
+ </main>
201
+ </>
202
+ )}
203
+ </div>
204
+ </div>
205
+ );
206
+ }
207
+
208
+ export default DeployButton;
src/components/load-button/load-button.tsx ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import classNames from "classnames";
2
+ import { useState } from "react";
3
+ import { toast } from "react-toastify";
4
+
5
+ import SpaceIcon from "@/assets/space.svg";
6
+ import Loading from "../loading/loading";
7
+ import { Auth } from "../../../utils/types";
8
+
9
+ function LoadButton({
10
+ auth,
11
+ setHtml,
12
+ }: {
13
+ auth?: Auth;
14
+ setHtml: (html: string) => void;
15
+ }) {
16
+ const [open, setOpen] = useState(false);
17
+ const [loading, setLoading] = useState(false);
18
+ const [error, setError] = useState(false);
19
+ const [path, setPath] = useState<string | undefined>(undefined);
20
+
21
+ const loadSpace = async () => {
22
+ setLoading(true);
23
+ try {
24
+ const res = await fetch(`/api/remix/${path}`);
25
+ const data = await res.json();
26
+ if (res.ok) {
27
+ if (data.html) {
28
+ setHtml(data.html);
29
+ toast.success("Project loaded successfully.");
30
+ }
31
+ setOpen(false);
32
+ } else {
33
+ toast.error(data.message);
34
+ setError(data.message);
35
+ }
36
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
37
+ } catch (error: any) {
38
+ toast.error(error.message);
39
+ setError(error.message);
40
+ }
41
+ setLoading(false);
42
+ };
43
+
44
+ return (
45
+ <div
46
+ className={classNames("max-md:hidden", {
47
+ "border-r border-gray-700 pr-5": auth,
48
+ })}
49
+ >
50
+ <p
51
+ className="underline hover:text-white cursor-pointer text-xs lg:text-sm text-gray-300"
52
+ onClick={() => setOpen(!open)}
53
+ >
54
+ Load project
55
+ </p>
56
+ <div
57
+ className={classNames(
58
+ "h-screen w-screen bg-black/20 fixed left-0 top-0 z-10",
59
+ {
60
+ "opacity-0 pointer-events-none": !open,
61
+ }
62
+ )}
63
+ onClick={() => setOpen(false)}
64
+ ></div>
65
+ <div
66
+ className={classNames(
67
+ "absolute top-[calc(100%+8px)] right-2 z-10 w-80 bg-white border border-gray-200 rounded-lg shadow-lg transition-all duration-75 overflow-hidden",
68
+ {
69
+ "opacity-0 pointer-events-none": !open,
70
+ }
71
+ )}
72
+ >
73
+ <>
74
+ <header className="flex items-center text-sm px-4 py-2 border-b border-gray-200 gap-2 bg-gray-100 font-semibold text-gray-700">
75
+ <span className="text-xs bg-pink-500/10 text-pink-500 rounded-full pl-1.5 pr-2.5 py-0.5 flex items-center justify-start gap-1.5">
76
+ <img src={SpaceIcon} alt="Space Icon" className="size-4" />
77
+ Space
78
+ </span>
79
+ Load Project
80
+ </header>
81
+ <main className="px-4 pt-3 pb-4 space-y-3">
82
+ <label className="block">
83
+ <p className="text-gray-600 text-sm font-medium mb-1.5">
84
+ Space URL
85
+ </p>
86
+ <input
87
+ type="text"
88
+ value={path}
89
+ className="mr-2 border rounded-md px-3 py-1.5 border-gray-300 w-full text-sm"
90
+ placeholder="https://huggingface.co/spaces/username/space-name"
91
+ onChange={(e) => setPath(e.target.value)}
92
+ onFocus={() => setError(false)}
93
+ onBlur={(e) => {
94
+ const pathParts = e.target.value.split("/");
95
+ setPath(
96
+ `${pathParts[pathParts.length - 2]}/${
97
+ pathParts[pathParts.length - 1]
98
+ }`
99
+ );
100
+ setError(false);
101
+ }}
102
+ />
103
+ </label>
104
+ {error && (
105
+ <p className="text-red-500 text-xs bg-red-500/10 rounded-md p-2 break-all">
106
+ {error}
107
+ </p>
108
+ )}
109
+ <div className="pt-2 text-right">
110
+ <button
111
+ disabled={error || loading || !path}
112
+ className="relative rounded-full bg-black px-5 py-2 text-white font-semibold text-xs hover:bg-black/90 transition-all duration-100 disabled:bg-gray-300 disabled:text-gray-500 disabled:cursor-not-allowed disabled:hover:bg-gray-300"
113
+ onClick={loadSpace}
114
+ >
115
+ Load Project
116
+ {loading && <Loading />}
117
+ </button>
118
+ </div>
119
+ </main>
120
+ </>
121
+ </div>
122
+ </div>
123
+ );
124
+ }
125
+ export default LoadButton;