|
1 | | -// import { useRouter } from "next/router"; |
2 | | -// import { useCallback, useEffect, useState } from "react"; |
3 | | -// import { useTeam } from "@/context/team-context"; |
4 | | -// import { PlusIcon } from "lucide-react"; |
5 | | -// import { HexColorInput, HexColorPicker } from "react-colorful"; |
6 | | -// import { toast } from "sonner"; |
7 | | -// import { mutate } from "swr"; |
8 | | -// import { UpgradePlanModal } from "@/components/billing/upgrade-plan-modal"; |
9 | | -// import AppLayout from "@/components/layouts/app"; |
10 | | -// import Navbar from "@/components/settings/navbar"; |
11 | | -// import { Button } from "@/components/ui/button"; |
12 | | -// import { Card, CardContent, CardFooter } from "@/components/ui/card"; |
13 | | -// import { Label } from "@/components/ui/label"; |
14 | | -// import LoadingSpinner from "@/components/ui/loading-spinner"; |
15 | | -// import { |
16 | | -// Popover, |
17 | | -// PopoverContent, |
18 | | -// PopoverTrigger, |
19 | | -// } from "@/components/ui/popover"; |
20 | | -// import { usePlan } from "@/lib/swr/use-billing"; |
21 | | -// import { useBrand } from "@/lib/swr/use-brand"; |
22 | | -// import { convertDataUrlToFile, uploadImage } from "@/lib/utils"; |
23 | | -// export default function Branding() { |
24 | | -// const { brand } = useBrand(); |
25 | | -// const teamInfo = useTeam(); |
26 | | -// const router = useRouter(); |
27 | | -// const { plan } = usePlan(); |
28 | | -// const [brandColor, setBrandColor] = useState<string>("#000000"); |
29 | | -// const [logo, setLogo] = useState<string | null>(null); |
30 | | -// const [isLoading, setIsLoading] = useState<boolean>(false); |
31 | | -// const [fileError, setFileError] = useState<string | null>(null); |
32 | | -// const [dragActive, setDragActive] = useState(false); |
33 | | -// const onChangeLogo = useCallback( |
34 | | -// (e: any) => { |
35 | | -// setFileError(null); |
36 | | -// const file = e.target.files[0]; |
37 | | -// if (file) { |
38 | | -// if (file.size / 1024 / 1024 > 2) { |
39 | | -// setFileError("File size too big (max 2MB)"); |
40 | | -// } else if (file.type !== "image/png" && file.type !== "image/jpeg") { |
41 | | -// setFileError("File type not supported (.png or .jpg only)"); |
42 | | -// } else { |
43 | | -// const reader = new FileReader(); |
44 | | -// reader.onload = (e) => { |
45 | | -// setLogo(e.target?.result as string); |
46 | | -// }; |
47 | | -// reader.readAsDataURL(file); |
48 | | -// } |
49 | | -// } |
50 | | -// }, |
51 | | -// [setLogo], |
52 | | -// ); |
53 | | -// useEffect(() => { |
54 | | -// if (brand) { |
55 | | -// setBrandColor(brand.brandColor || "#000000"); |
56 | | -// setLogo(brand.logo || null); |
57 | | -// } |
58 | | -// }, [brand]); |
59 | | -// const saveBranding = async (e: any) => { |
60 | | -// e.preventDefault(); |
61 | | -// setIsLoading(true); |
62 | | -// // Upload the image if it's a data URL |
63 | | -// let blobUrl: string | null = logo && logo.startsWith("data:") ? null : logo; |
64 | | -// if (logo && logo.startsWith("data:")) { |
65 | | -// // Convert the data URL to a blob |
66 | | -// const blob = convertDataUrlToFile({ dataUrl: logo }); |
67 | | -// // Upload the blob to vercel storage |
68 | | -// blobUrl = await uploadImage(blob); |
69 | | -// setLogo(blobUrl); |
70 | | -// } |
71 | | -// const data = { |
72 | | -// brandColor: brandColor, |
73 | | -// logo: blobUrl, |
74 | | -// }; |
75 | | -// const res = await fetch( |
76 | | -// `/api/teams/${teamInfo?.currentTeam?.id}/branding`, |
77 | | -// { |
78 | | -// method: brand ? "PUT" : "POST", |
79 | | -// body: JSON.stringify(data), |
80 | | -// headers: { |
81 | | -// "Content-Type": "application/json", |
82 | | -// }, |
83 | | -// }, |
84 | | -// ); |
85 | | -// if (res.ok) { |
86 | | -// mutate(`/api/teams/${teamInfo?.currentTeam?.id}/branding`); |
87 | | -// setIsLoading(false); |
88 | | -// toast.success("Branding updated successfully"); |
89 | | -// } |
90 | | -// }; |
91 | | -// const handleDelete = async () => { |
92 | | -// setIsLoading(true); |
93 | | -// const res = await fetch( |
94 | | -// `/api/teams/${teamInfo?.currentTeam?.id}/branding`, |
95 | | -// { |
96 | | -// method: "DELETE", |
97 | | -// headers: { |
98 | | -// "Content-Type": "application/json", |
99 | | -// }, |
100 | | -// }, |
101 | | -// ); |
102 | | -// if (res.ok) { |
103 | | -// setLogo(null); |
104 | | -// setBrandColor("#000000"); |
105 | | -// setIsLoading(false); |
106 | | -// toast.success("Branding reset successfully"); |
107 | | -// router.reload(); |
108 | | -// } |
109 | | -// }; |
110 | | -// return ( |
111 | | -// <AppLayout> |
112 | | -// <Navbar current="Branding" /> |
113 | | -// <div className="p-4 sm:m-4 sm:p-4"> |
114 | | -// <div className="mb-4 flex items-center justify-between md:mb-8 lg:mb-12"> |
115 | | -// <div className="space-y-1"> |
116 | | -// <h3 className="text-2xl font-semibold tracking-tight text-foreground"> |
117 | | -// Branding |
118 | | -// </h3> |
119 | | -// <p className="text-sm text-muted-foreground"> |
120 | | -// Customize how your brand appears globally across Papermark |
121 | | -// documents your visitors see. |
122 | | -// </p> |
123 | | -// </div> |
124 | | -// </div> |
125 | | -// <div> |
126 | | -// <Card className="dark:bg-secondary"> |
127 | | -// <CardContent className="pt-6"> |
128 | | -// <div className="grid gap-6"> |
129 | | -// <div className="flex flex-col gap-2"> |
130 | | -// <div className="flex items-center justify-between"> |
131 | | -// <Label htmlFor="logo"> |
132 | | -// Logo{" "} |
133 | | -// <span className="text-sm italic text-muted-foreground"> |
134 | | -// (max 2 MB) |
135 | | -// </span> |
136 | | -// </Label> |
137 | | -// {fileError ? ( |
138 | | -// <p className="text-sm text-red-500">{fileError}</p> |
139 | | -// ) : null} |
140 | | -// </div> |
141 | | -// <label |
142 | | -// htmlFor="image" |
143 | | -// className="group relative mt-1 flex h-[4rem] w-[12rem] cursor-pointer flex-col items-center justify-center rounded-md border border-gray-300 bg-white shadow-sm transition-all hover:bg-gray-50" |
144 | | -// style={{ |
145 | | -// backgroundImage: |
146 | | -// "linear-gradient(45deg, #ccc 25%, transparent 25%), linear-gradient(135deg, #ccc 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #ccc 75%), linear-gradient(135deg, transparent 75%, #ccc 75%)", |
147 | | -// backgroundSize: "20px 20px", |
148 | | -// backgroundPosition: "0 0, 10px 0, 10px -10px, 0px 10px", |
149 | | -// }} |
150 | | -// > |
151 | | -// {false && ( |
152 | | -// <div className="absolute z-[5] flex h-full w-full items-center justify-center rounded-md bg-white"> |
153 | | -// <LoadingSpinner /> |
154 | | -// </div> |
155 | | -// )} |
156 | | -// <div |
157 | | -// className="absolute z-[5] h-full w-full rounded-md" |
158 | | -// onDragOver={(e) => { |
159 | | -// e.preventDefault(); |
160 | | -// e.stopPropagation(); |
161 | | -// setDragActive(true); |
162 | | -// }} |
163 | | -// onDragEnter={(e) => { |
164 | | -// e.preventDefault(); |
165 | | -// e.stopPropagation(); |
166 | | -// setDragActive(true); |
167 | | -// }} |
168 | | -// onDragLeave={(e) => { |
169 | | -// e.preventDefault(); |
170 | | -// e.stopPropagation(); |
171 | | -// setDragActive(false); |
172 | | -// }} |
173 | | -// onDrop={(e) => { |
174 | | -// e.preventDefault(); |
175 | | -// e.stopPropagation(); |
176 | | -// setDragActive(false); |
177 | | -// setFileError(null); |
178 | | -// const file = |
179 | | -// e.dataTransfer.files && e.dataTransfer.files[0]; |
180 | | -// if (file) { |
181 | | -// if (file.size / 1024 / 1024 > 2) { |
182 | | -// setFileError("File size too big (max 2MB)"); |
183 | | -// } else if ( |
184 | | -// file.type !== "image/png" && |
185 | | -// file.type !== "image/jpeg" |
186 | | -// ) { |
187 | | -// setFileError( |
188 | | -// "File type not supported (.png or .jpg only)", |
189 | | -// ); |
190 | | -// } else { |
191 | | -// const reader = new FileReader(); |
192 | | -// reader.onload = (e) => { |
193 | | -// setLogo(e.target?.result as string); |
194 | | -// }; |
195 | | -// reader.readAsDataURL(file); |
196 | | -// } |
197 | | -// } |
198 | | -// }} |
199 | | -// /> |
200 | | -// <div |
201 | | -// className={`${ |
202 | | -// dragActive |
203 | | -// ? "cursor-copy border-2 border-black bg-gray-50 opacity-100" |
204 | | -// : "" |
205 | | -// } absolute z-[3] flex h-full w-full flex-col items-center justify-center rounded-md bg-white transition-all ${ |
206 | | -// logo |
207 | | -// ? "opacity-0 group-hover:opacity-100" |
208 | | -// : "group-hover:bg-gray-50" |
209 | | -// }`} |
210 | | -// > |
211 | | -// <PlusIcon |
212 | | -// className={`${ |
213 | | -// dragActive ? "scale-110" : "scale-100" |
214 | | -// } h-7 w-7 text-gray-500 transition-all duration-75 group-hover:scale-110 group-active:scale-95`} |
215 | | -// /> |
216 | | -// <span className="sr-only">OG image upload</span> |
217 | | -// </div> |
218 | | -// {logo && ( |
219 | | -// <img |
220 | | -// src={logo} |
221 | | -// alt="Preview" |
222 | | -// className="h-full w-full rounded-md object-contain" |
223 | | -// /> |
224 | | -// )} |
225 | | -// </label> |
226 | | -// <div className="mt-1 flex rounded-md shadow-sm"> |
227 | | -// <input |
228 | | -// id="image" |
229 | | -// name="image" |
230 | | -// type="file" |
231 | | -// accept="image/jpeg,image/png" |
232 | | -// className="sr-only" |
233 | | -// onChange={onChangeLogo} |
234 | | -// /> |
235 | | -// </div> |
236 | | -// </div> |
237 | | -// <div className="flex flex-col gap-2"> |
238 | | -// <Label htmlFor="primary-color">Brand Color</Label> |
239 | | -// <div className="flex space-x-1"> |
240 | | -// <Popover> |
241 | | -// <PopoverTrigger> |
242 | | -// <div |
243 | | -// className="h-9 w-9 cursor-pointer rounded-md shadow-sm ring-1 ring-muted-foreground hover:ring-1 hover:ring-gray-300" |
244 | | -// style={{ backgroundColor: brandColor }} |
245 | | -// /> |
246 | | -// </PopoverTrigger> |
247 | | -// <PopoverContent> |
248 | | -// <HexColorPicker |
249 | | -// color={brandColor} |
250 | | -// onChange={setBrandColor} |
251 | | -// /> |
252 | | -// </PopoverContent> |
253 | | -// </Popover> |
254 | | -// <HexColorInput |
255 | | -// className="flex h-9 w-full rounded-md border-0 bg-background px-3 py-2 text-sm shadow-sm ring-1 ring-muted-foreground placeholder:text-muted-foreground focus:border-0 focus:ring-1 focus:ring-gray-300" |
256 | | -// color={brandColor} |
257 | | -// onChange={setBrandColor} |
258 | | -// prefixed |
259 | | -// /> |
260 | | -// </div> |
261 | | -// </div> |
262 | | -// </div> |
263 | | -// </CardContent> |
264 | | -// <CardFooter className="border-t p-6"> |
265 | | -// {plan === "free" ? ( |
266 | | -// <UpgradePlanModal clickedPlan="Pro" trigger={"branding_page"}> |
267 | | -// <Button>Upgrade to Save Branding</Button> |
268 | | -// </UpgradePlanModal> |
269 | | -// ) : ( |
270 | | -// <Button onClick={saveBranding} loading={isLoading}> |
271 | | -// Save changes |
272 | | -// </Button> |
273 | | -// )} |
274 | | -// {/* delete button */} |
275 | | -// <Button variant="link" onClick={handleDelete} disabled={!brand}> |
276 | | -// Reset branding |
277 | | -// </Button> |
278 | | -// </CardFooter> |
279 | | -// </Card> |
280 | | -// </div> |
281 | | -// </div> |
282 | | -// </AppLayout> |
283 | | -// ); |
284 | | -// } |
285 | 1 | import { useRouter } from "next/router"; |
286 | 2 |
|
287 | 3 | import { useCallback, useEffect, useState } from "react"; |
|
0 commit comments