nomadicsynth commited on
Commit
4a1e66b
·
verified ·
2 Parent(s): fa8588b 9d4895e

Merge branch #enzostvs/deepsite' into 'nomadicsynth/deepsite'

Browse files
server.js CHANGED
@@ -42,10 +42,16 @@ const getPTag = (repoId) => {
42
  };
43
 
44
  app.get("/api/login", (_req, res) => {
45
- res.redirect(
46
- 302,
47
- `https://huggingface.co/oauth/authorize?client_id=${process.env.OAUTH_CLIENT_ID}&redirect_uri=${REDIRECT_URI}&response_type=code&scope=openid%20profile%20write-repos%20manage-repos%20inference-api&prompt=consent&state=1234567890`
48
- );
 
 
 
 
 
 
49
  });
50
  app.get("/auth/login", async (req, res) => {
51
  const { code } = req.query;
@@ -120,7 +126,7 @@ app.get("/api/@me", checkUser, async (req, res) => {
120
 
121
  app.post("/api/deploy", checkUser, async (req, res) => {
122
  const { html, title, path } = req.body;
123
- if (!html || !title) {
124
  return res.status(400).send({
125
  ok: false,
126
  message: "Missing required fields",
@@ -352,33 +358,62 @@ app.get("/api/remix/:username/:repo", async (req, res) => {
352
  const token = hf_token || process.env.DEFAULT_HF_TOKEN;
353
 
354
  const repoId = `${username}/${repo}`;
355
- const space = await spaceInfo({
356
- name: repoId,
357
- });
358
 
359
- if (!space || space.sdk !== "static" || space.private) {
360
- return res.status(404).send({
361
- ok: false,
362
- message: "Space not found",
 
 
363
  });
364
- }
365
 
366
- const url = `https://huggingface.co/spaces/${repoId}/raw/main/index.html`;
367
- const response = await fetch(url);
368
- if (!response.ok) {
369
- return res.status(404).send({
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
370
  ok: false,
371
- message: "Space not found",
372
  });
373
  }
374
- let html = await response.text();
375
- // remove the last p tag including this url https://enzostvs-deepsite.hf.space
376
- html = html.replace(getPTag(repoId), "");
377
-
378
- res.status(200).send({
379
- ok: true,
380
- html,
381
- });
382
  });
383
 
384
  app.get("*", (_req, res) => {
 
42
  };
43
 
44
  app.get("/api/login", (_req, res) => {
45
+ // res.redirect(
46
+ // 302,
47
+ // `https://huggingface.co/oauth/authorize?client_id=${process.env.OAUTH_CLIENT_ID}&redirect_uri=${REDIRECT_URI}&response_type=code&scope=openid%20profile%20write-repos%20manage-repos%20inference-api&prompt=consent&state=1234567890`
48
+ // );
49
+ // redirect in new tab
50
+ const redirectUrl = `https://huggingface.co/oauth/authorize?client_id=${process.env.OAUTH_CLIENT_ID}&redirect_uri=${REDIRECT_URI}&response_type=code&scope=openid%20profile%20write-repos%20manage-repos%20inference-api&prompt=consent&state=1234567890`;
51
+ res.status(200).send({
52
+ ok: true,
53
+ redirectUrl,
54
+ });
55
  });
56
  app.get("/auth/login", async (req, res) => {
57
  const { code } = req.query;
 
126
 
127
  app.post("/api/deploy", checkUser, async (req, res) => {
128
  const { html, title, path } = req.body;
129
+ if (!html || (!path && !title)) {
130
  return res.status(400).send({
131
  ok: false,
132
  message: "Missing required fields",
 
358
  const token = hf_token || process.env.DEFAULT_HF_TOKEN;
359
 
360
  const repoId = `${username}/${repo}`;
 
 
 
361
 
362
+ const url = `https://huggingface.co/spaces/${repoId}/raw/main/index.html`;
363
+ try {
364
+ const space = await spaceInfo({
365
+ name: repoId,
366
+ accessToken: token,
367
+ additionalFields: ["author"],
368
  });
 
369
 
370
+ if (!space || space.sdk !== "static" || space.private) {
371
+ return res.status(404).send({
372
+ ok: false,
373
+ message: "Space not found",
374
+ });
375
+ }
376
+
377
+ const response = await fetch(url);
378
+ if (!response.ok) {
379
+ return res.status(404).send({
380
+ ok: false,
381
+ message: "Space not found",
382
+ });
383
+ }
384
+ let html = await response.text();
385
+ // remove the last p tag including this url https://enzostvs-deepsite.hf.space
386
+ html = html.replace(getPTag(repoId), "");
387
+
388
+ let user = null;
389
+
390
+ if (token) {
391
+ const request_user = await fetch(
392
+ "https://huggingface.co/oauth/userinfo",
393
+ {
394
+ headers: {
395
+ Authorization: `Bearer ${hf_token}`,
396
+ },
397
+ }
398
+ )
399
+ .then((res) => res.json())
400
+ .catch(() => null);
401
+
402
+ user = request_user;
403
+ }
404
+
405
+ res.status(200).send({
406
+ ok: true,
407
+ html,
408
+ isOwner: space.author === user?.preferred_username,
409
+ path: repoId,
410
+ });
411
+ } catch (error) {
412
+ return res.status(500).send({
413
  ok: false,
414
+ message: error.message,
415
  });
416
  }
 
 
 
 
 
 
 
 
417
  });
418
 
419
  app.get("*", (_req, res) => {
src/components/App.tsx CHANGED
@@ -182,7 +182,7 @@ function App() {
182
  }
183
  }}
184
  >
185
- <DeployButton html={html} error={error} auth={auth} />
186
  </Header>
187
  <main className="max-lg:flex-col flex w-full">
188
  <div
 
182
  }
183
  }}
184
  >
185
+ <DeployButton html={html} error={error} auth={auth} setHtml={setHtml} />
186
  </Header>
187
  <main className="max-lg:flex-col flex w-full">
188
  <div
src/components/deploy-button/deploy-button.tsx CHANGED
@@ -8,6 +8,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">
@@ -27,10 +28,12 @@ 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);
@@ -78,128 +81,131 @@ function DeployButton({
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
  );
 
8
  import Loading from "../loading/loading";
9
  import Login from "../login/login";
10
  import { Auth } from "./../../../utils/types";
11
+ import LoadButton from "../load-button/load-button";
12
 
13
  const MsgToast = ({ url }: { url: string }) => (
14
  <div className="w-full flex items-center justify-center gap-3">
 
28
  html,
29
  error = false,
30
  auth,
31
+ setHtml,
32
  }: {
33
  html: string;
34
  error: boolean;
35
  auth?: Auth;
36
+ setHtml: (html: string) => void;
37
  }) {
38
  const [open, setOpen] = useState(false);
39
  const [loading, setLoading] = useState(false);
 
81
  };
82
 
83
  return (
84
+ <div className="flex items-center justify-end gap-5">
85
+ <LoadButton auth={auth} setHtml={setHtml} setPath={setPath} />
86
+ <div className="relative flex items-center justify-end">
87
+ {auth && (
88
+ <>
89
+ <button
90
+ className="mr-2 cursor-pointer"
91
+ onClick={() => {
92
+ if (confirm("Are you sure you want to log out?")) {
93
+ // go to /auth/logout page
94
+ window.location.href = "/auth/logout";
95
+ }
96
+ }}
 
 
 
 
 
 
 
97
  >
98
+ <FaPowerOff className="text-lg text-red-500" />
99
+ </button>
100
+ <p className="mr-3 text-xs lg:text-sm text-gray-300">
101
+ <span className="max-lg:hidden">Connected as </span>
102
+ <a
103
+ href={`https://huggingface.co/${auth.preferred_username}`}
104
+ target="_blank"
105
+ className="underline hover:text-white"
106
+ >
107
+ {auth.preferred_username}
108
+ </a>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  </>
111
  )}
112
+ <button
113
+ className={classNames(
114
+ "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",
115
+ {
116
+ "bg-pink-400": open,
117
+ "bg-pink-500": !open,
118
+ }
119
+ )}
120
+ onClick={() => setOpen(!open)}
121
+ >
122
+ {path ? "Update Space" : "Deploy to Space"}
123
+ </button>
124
+ <div
125
+ className={classNames(
126
+ "h-screen w-screen bg-black/20 fixed left-0 top-0 z-10",
127
+ {
128
+ "opacity-0 pointer-events-none": !open,
129
+ }
130
+ )}
131
+ onClick={() => setOpen(false)}
132
+ ></div>
133
+ <div
134
+ className={classNames(
135
+ "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",
136
+ {
137
+ "opacity-0 pointer-events-none": !open,
138
+ }
139
+ )}
140
+ >
141
+ {!auth ? (
142
+ <Login html={html}>
143
+ <p className="text-gray-500 text-sm mb-3">
144
+ Host this project for free and share it with your friends.
145
+ </p>
146
+ </Login>
147
+ ) : (
148
+ <>
149
+ <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">
150
+ <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">
151
+ <img src={SpaceIcon} alt="Space Icon" className="size-4" />
152
+ Space
153
+ </span>
154
+ Configure Deployment
155
+ </header>
156
+ <main className="px-4 pt-3 pb-4 space-y-3">
157
+ <p className="text-xs text-amber-600 bg-amber-500/10 rounded-md p-2">
158
+ {path ? (
159
+ <span>
160
+ Your space is live at{" "}
161
+ <a
162
+ href={`https://huggingface.co/spaces/${path}`}
163
+ target="_blank"
164
+ className="underline hover:text-amber-700"
165
+ >
166
+ huggingface.co/{path}
167
+ </a>
168
+ . You can update it by deploying again.
169
+ </span>
170
+ ) : (
171
+ "Deploy your project to a space on the Hub. Spaces are a way to share your project with the world."
172
+ )}
173
+ </p>
174
+ {!path && (
175
+ <label className="block">
176
+ <p className="text-gray-600 text-sm font-medium mb-1.5">
177
+ Space Title
178
+ </p>
179
+ <input
180
+ type="text"
181
+ value={config.title}
182
+ className="mr-2 border rounded-md px-3 py-1.5 border-gray-300 w-full text-sm"
183
+ placeholder="My Awesome Space"
184
+ onChange={(e) =>
185
+ setConfig({ ...config, title: e.target.value })
186
+ }
187
+ />
188
+ </label>
189
+ )}
190
+ {error && (
191
+ <p className="text-red-500 text-xs bg-red-500/10 rounded-md p-2">
192
+ Your code has errors. Fix them before deploying.
193
+ </p>
194
+ )}
195
+ <div className="pt-2 text-right">
196
+ <button
197
+ disabled={error || loading || (!path && !config.title)}
198
+ 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"
199
+ onClick={createSpace}
200
+ >
201
+ {path ? "Update Space" : "Create Space"}
202
+ {loading && <Loading />}
203
+ </button>
204
+ </div>
205
+ </main>
206
+ </>
207
+ )}
208
+ </div>
209
  </div>
210
  </div>
211
  );
src/components/load-button/load-button.tsx ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ setPath,
13
+ }: {
14
+ auth?: Auth;
15
+ setHtml: (html: string) => void;
16
+ setPath: (path?: string) => void;
17
+ }) {
18
+ const [open, setOpen] = useState(false);
19
+ const [loading, setLoading] = useState(false);
20
+ const [error, setError] = useState(false);
21
+ const [url, setUrl] = useState<string | undefined>(undefined);
22
+
23
+ const loadSpace = async () => {
24
+ setLoading(true);
25
+ try {
26
+ const res = await fetch(`/api/remix/${url}`);
27
+ const data = await res.json();
28
+ if (res.ok) {
29
+ if (data.html) {
30
+ setHtml(data.html);
31
+ toast.success("Project loaded successfully.");
32
+ }
33
+ if (data.isOwner) {
34
+ setPath(data.path);
35
+ } else {
36
+ setPath(undefined);
37
+ }
38
+ setOpen(false);
39
+ } else {
40
+ toast.error(data.message);
41
+ setError(data.message);
42
+ }
43
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
44
+ } catch (error: any) {
45
+ toast.error(error.message);
46
+ setError(error.message);
47
+ }
48
+ setLoading(false);
49
+ };
50
+
51
+ return (
52
+ <div
53
+ className={classNames("max-md:hidden", {
54
+ "border-r border-gray-700 pr-5": auth,
55
+ })}
56
+ >
57
+ <p
58
+ className="underline hover:text-white cursor-pointer text-xs lg:text-sm text-gray-300"
59
+ onClick={() => setOpen(!open)}
60
+ >
61
+ Load project
62
+ </p>
63
+ <div
64
+ className={classNames(
65
+ "h-screen w-screen bg-black/20 fixed left-0 top-0 z-10",
66
+ {
67
+ "opacity-0 pointer-events-none": !open,
68
+ }
69
+ )}
70
+ onClick={() => setOpen(false)}
71
+ ></div>
72
+ <div
73
+ className={classNames(
74
+ "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",
75
+ {
76
+ "opacity-0 pointer-events-none": !open,
77
+ }
78
+ )}
79
+ >
80
+ <>
81
+ <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">
82
+ <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">
83
+ <img src={SpaceIcon} alt="Space Icon" className="size-4" />
84
+ Space
85
+ </span>
86
+ Load Project
87
+ </header>
88
+ <main className="px-4 pt-3 pb-4 space-y-3">
89
+ <label className="block">
90
+ <p className="text-gray-600 text-sm font-medium mb-1.5">
91
+ Space URL
92
+ </p>
93
+ <input
94
+ type="text"
95
+ value={url}
96
+ className="mr-2 border rounded-md px-3 py-1.5 border-gray-300 w-full text-sm"
97
+ placeholder="https://huggingface.co/spaces/username/space-name"
98
+ onChange={(e) => setUrl(e.target.value)}
99
+ onFocus={() => setError(false)}
100
+ onBlur={(e) => {
101
+ const pathParts = e.target.value.split("/");
102
+ setUrl(
103
+ `${pathParts[pathParts.length - 2]}/${
104
+ pathParts[pathParts.length - 1]
105
+ }`
106
+ );
107
+ setError(false);
108
+ }}
109
+ />
110
+ </label>
111
+ {error && (
112
+ <p className="text-red-500 text-xs bg-red-500/10 rounded-md p-2 break-all">
113
+ {error}
114
+ </p>
115
+ )}
116
+ <div className="pt-2 text-right">
117
+ <button
118
+ disabled={error || loading || !url}
119
+ 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"
120
+ onClick={loadSpace}
121
+ >
122
+ Load Project
123
+ {loading && <Loading />}
124
+ </button>
125
+ </div>
126
+ </main>
127
+ </>
128
+ </div>
129
+ </div>
130
+ );
131
+ }
132
+ export default LoadButton;
src/components/login/login.tsx CHANGED
@@ -10,10 +10,15 @@ function Login({
10
  }) {
11
  const [, setStorage] = useLocalStorage("html_content");
12
 
13
- const handleClick = () => {
14
  if (html !== defaultHTML) {
15
  setStorage(html);
16
  }
 
 
 
 
 
17
  };
18
 
19
  return (
@@ -26,13 +31,13 @@ function Login({
26
  </header>
27
  <main className="px-4 py-4 space-y-3">
28
  {children}
29
- <a href="/api/login" onClick={handleClick}>
30
  <img
31
  src="https://huggingface.co/datasets/huggingface/badges/resolve/main/sign-in-with-huggingface-lg-dark.svg"
32
  alt="Sign in with Hugging Face"
33
  className="mx-auto"
34
  />
35
- </a>
36
  </main>
37
  </>
38
  );
 
10
  }) {
11
  const [, setStorage] = useLocalStorage("html_content");
12
 
13
+ const handleClick = async () => {
14
  if (html !== defaultHTML) {
15
  setStorage(html);
16
  }
17
+ const request = await fetch("/api/login");
18
+ const res = await request.json();
19
+ if (res?.redirectUrl) {
20
+ window.open(res.redirectUrl, "_blank");
21
+ }
22
  };
23
 
24
  return (
 
31
  </header>
32
  <main className="px-4 py-4 space-y-3">
33
  {children}
34
+ <button onClick={handleClick}>
35
  <img
36
  src="https://huggingface.co/datasets/huggingface/badges/resolve/main/sign-in-with-huggingface-lg-dark.svg"
37
  alt="Sign in with Hugging Face"
38
  className="mx-auto"
39
  />
40
+ </button>
41
  </main>
42
  </>
43
  );
src/main.tsx CHANGED
@@ -7,6 +7,6 @@ import App from "./components/App.tsx";
7
  createRoot(document.getElementById("root")!).render(
8
  <StrictMode>
9
  <App />
10
- <ToastContainer />
11
  </StrictMode>
12
  );
 
7
  createRoot(document.getElementById("root")!).render(
8
  <StrictMode>
9
  <App />
10
+ <ToastContainer className="pt-11 max-md:p-4" />
11
  </StrictMode>
12
  );