office-viewer-react 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +390 -0
- package/dist/client/assets/DocxViewer-NgAiZAEg.css +1 -0
- package/dist/client/assets/DocxViewer-gwdjm0mw.js +60 -0
- package/dist/client/assets/LogoIcon-BcnkueZW.js +1 -0
- package/dist/client/assets/PptxViewer-CLNaZa_4.js +59 -0
- package/dist/client/assets/PptxViewer-CYMXzyIj.css +1 -0
- package/dist/client/assets/XlsxViewer-BNso6L-X.css +1 -0
- package/dist/client/assets/XlsxViewer-C2ErMokS.js +64 -0
- package/dist/client/assets/_commonjs-dynamic-modules-DaXrHM_S.js +1 -0
- package/dist/client/assets/form-C1byQJR4.js +1 -0
- package/dist/client/assets/index-BDMLGHcR.js +2 -0
- package/dist/client/assets/index-CKjGwz9R.js +12 -0
- package/dist/client/assets/jszip.min-BwIaN_vk.js +2 -0
- package/dist/client/assets/login-DEy3R1iD.js +1 -0
- package/dist/client/assets/register-CUUVGLJE.js +1 -0
- package/dist/client/assets/styles-3a3CPFIV.css +1 -0
- package/dist/client/robots.txt +2 -0
- package/dist/index.cjs +1806 -0
- package/dist/index.d.cts +16 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +1769 -0
- package/dist/server/assets/DocxViewer-Bm8UJY-7.js +469 -0
- package/dist/server/assets/LogoIcon-Dx0LU3or.js +26 -0
- package/dist/server/assets/PptxViewer-DS7Atucw.js +213 -0
- package/dist/server/assets/XlsxViewer-jzIgKmN2.js +841 -0
- package/dist/server/assets/_tanstack-start-manifest_v-CpFqMvFH.js +4 -0
- package/dist/server/assets/empty-plugin-adapters-BFgPZ6_d.js +6 -0
- package/dist/server/assets/form-CD9otjw-.js +236 -0
- package/dist/server/assets/index-gQHSGxNv.js +365 -0
- package/dist/server/assets/login-DvbAXNSQ.js +81 -0
- package/dist/server/assets/register-C2G9K9kP.js +102 -0
- package/dist/server/assets/router-F5YKPXkV.js +229 -0
- package/dist/server/assets/server-6Sfy37dh.js +1523 -0
- package/dist/server/assets/start-dMGD6DUy.js +56 -0
- package/dist/server/server.js +94 -0
- package/package.json +120 -0
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
const tsrStartManifest = () => ({ routes: { __root__: { filePath: "C:/Users/balambika.t_virtuala/Desktop/Office-Viewer-app/office-viewer/frontend/src/routes/__root.tsx", children: ["/", "/login", "/register", "/sitemap.xml"], preloads: ["/assets/index-CKjGwz9R.js"], scripts: [{ attrs: { type: "module", async: true, src: "/assets/index-CKjGwz9R.js" } }] }, "/": { filePath: "C:/Users/balambika.t_virtuala/Desktop/Office-Viewer-app/office-viewer/frontend/src/routes/index.tsx", children: void 0, preloads: ["/assets/index-BDMLGHcR.js", "/assets/LogoIcon-BcnkueZW.js"] }, "/login": { filePath: "C:/Users/balambika.t_virtuala/Desktop/Office-Viewer-app/office-viewer/frontend/src/routes/login.tsx", children: void 0, preloads: ["/assets/login-DEy3R1iD.js", "/assets/form-C1byQJR4.js", "/assets/LogoIcon-BcnkueZW.js"] }, "/register": { filePath: "C:/Users/balambika.t_virtuala/Desktop/Office-Viewer-app/office-viewer/frontend/src/routes/register.tsx", children: void 0, preloads: ["/assets/register-CUUVGLJE.js", "/assets/form-C1byQJR4.js", "/assets/LogoIcon-BcnkueZW.js"] } } });
|
|
2
|
+
export {
|
|
3
|
+
tsrStartManifest
|
|
4
|
+
};
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { L as LogoIcon } from "./LogoIcon-Dx0LU3or.js";
|
|
3
|
+
import { ShieldCheck, HardDrive, Layers, EyeOff, Eye } from "lucide-react";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
6
|
+
import { cva } from "class-variance-authority";
|
|
7
|
+
import { FormProvider, Controller, useFormContext } from "react-hook-form";
|
|
8
|
+
import { clsx } from "clsx";
|
|
9
|
+
import { twMerge } from "tailwind-merge";
|
|
10
|
+
import * as LabelPrimitive from "@radix-ui/react-label";
|
|
11
|
+
function BrandMark() {
|
|
12
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-3", children: [
|
|
13
|
+
/* @__PURE__ */ jsx(
|
|
14
|
+
"div",
|
|
15
|
+
{
|
|
16
|
+
className: "flex h-16 w-16 items-center justify-center rounded-2xl",
|
|
17
|
+
style: {
|
|
18
|
+
backgroundColor: "#2563eb",
|
|
19
|
+
boxShadow: "0 0 0 1px rgba(37,99,235,0.22), inset 0 1px 0 rgba(255,255,255,0.15), 0 6px 20px 0 rgba(37,99,235,0.30)"
|
|
20
|
+
},
|
|
21
|
+
children: /* @__PURE__ */ jsx(LogoIcon, { size: 30 })
|
|
22
|
+
}
|
|
23
|
+
),
|
|
24
|
+
/* @__PURE__ */ jsxs("div", { className: "flex select-none flex-col items-center", style: { gap: 2 }, children: [
|
|
25
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] font-semibold uppercase tracking-[0.22em] text-blue-600", children: "Office" }),
|
|
26
|
+
/* @__PURE__ */ jsx("span", { className: "text-[24px] font-bold leading-none tracking-[-0.02em] text-slate-900", children: "Viewer" })
|
|
27
|
+
] }),
|
|
28
|
+
/* @__PURE__ */ jsx("p", { className: "mt-0.5 max-w-xs text-center text-[13.5px] leading-relaxed text-slate-500", children: "Securely view Word, Excel and PowerPoint files directly in your browser." })
|
|
29
|
+
] });
|
|
30
|
+
}
|
|
31
|
+
function TrustIndicators() {
|
|
32
|
+
return /* @__PURE__ */ jsx("div", { className: "border-t border-slate-100 px-8 py-5 sm:px-12 sm:py-6", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-center gap-x-7 gap-y-3", children: [
|
|
33
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
34
|
+
/* @__PURE__ */ jsx(ShieldCheck, { className: "h-3.5 w-3.5 shrink-0 text-slate-400" }),
|
|
35
|
+
/* @__PURE__ */ jsx("span", { className: "whitespace-nowrap text-[11.5px] text-slate-400", children: "Secure Access" })
|
|
36
|
+
] }),
|
|
37
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
38
|
+
/* @__PURE__ */ jsx(HardDrive, { className: "h-3.5 w-3.5 shrink-0 text-slate-400" }),
|
|
39
|
+
/* @__PURE__ */ jsx("span", { className: "whitespace-nowrap text-[11.5px] text-slate-400", children: "Runs Locally" })
|
|
40
|
+
] }),
|
|
41
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
42
|
+
/* @__PURE__ */ jsx(Layers, { className: "h-3.5 w-3.5 shrink-0 text-slate-400" }),
|
|
43
|
+
/* @__PURE__ */ jsx("span", { className: "whitespace-nowrap text-[11.5px] text-slate-400", children: "Word, Excel & PowerPoint" })
|
|
44
|
+
] })
|
|
45
|
+
] }) });
|
|
46
|
+
}
|
|
47
|
+
function cn(...inputs) {
|
|
48
|
+
return twMerge(clsx(inputs));
|
|
49
|
+
}
|
|
50
|
+
const buttonVariants = cva(
|
|
51
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium cursor-pointer transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
52
|
+
{
|
|
53
|
+
variants: {
|
|
54
|
+
variant: {
|
|
55
|
+
default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
|
56
|
+
destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
|
|
57
|
+
outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
|
58
|
+
secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
|
59
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
60
|
+
link: "text-primary underline-offset-4 hover:underline"
|
|
61
|
+
},
|
|
62
|
+
size: {
|
|
63
|
+
default: "h-9 px-4 py-2",
|
|
64
|
+
sm: "h-8 rounded-md px-3 text-xs",
|
|
65
|
+
lg: "h-10 rounded-md px-8",
|
|
66
|
+
icon: "h-9 w-9"
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
defaultVariants: {
|
|
70
|
+
variant: "default",
|
|
71
|
+
size: "default"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
);
|
|
75
|
+
const Button = React.forwardRef(
|
|
76
|
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
77
|
+
const Comp = asChild ? Slot : "button";
|
|
78
|
+
return /* @__PURE__ */ jsx(Comp, { className: cn(buttonVariants({ variant, size, className })), ref, ...props });
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
Button.displayName = "Button";
|
|
82
|
+
const Input = React.forwardRef(
|
|
83
|
+
({ className, type, ...props }, ref) => {
|
|
84
|
+
return /* @__PURE__ */ jsx(
|
|
85
|
+
"input",
|
|
86
|
+
{
|
|
87
|
+
type,
|
|
88
|
+
className: cn(
|
|
89
|
+
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
90
|
+
className
|
|
91
|
+
),
|
|
92
|
+
ref,
|
|
93
|
+
...props
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
);
|
|
98
|
+
Input.displayName = "Input";
|
|
99
|
+
const PasswordInput = React.forwardRef(({ className, ...props }, ref) => {
|
|
100
|
+
const [visible, setVisible] = React.useState(false);
|
|
101
|
+
return /* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
102
|
+
/* @__PURE__ */ jsx(
|
|
103
|
+
Input,
|
|
104
|
+
{
|
|
105
|
+
type: visible ? "text" : "password",
|
|
106
|
+
className: cn("pr-10", className),
|
|
107
|
+
ref,
|
|
108
|
+
...props
|
|
109
|
+
}
|
|
110
|
+
),
|
|
111
|
+
/* @__PURE__ */ jsx(
|
|
112
|
+
"button",
|
|
113
|
+
{
|
|
114
|
+
type: "button",
|
|
115
|
+
tabIndex: -1,
|
|
116
|
+
"aria-label": visible ? "Hide password" : "Show password",
|
|
117
|
+
onClick: () => setVisible((v) => !v),
|
|
118
|
+
className: "absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 transition-colors hover:text-slate-600",
|
|
119
|
+
children: visible ? /* @__PURE__ */ jsx(EyeOff, { className: "h-4 w-4", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Eye, { className: "h-4 w-4", "aria-hidden": "true" })
|
|
120
|
+
}
|
|
121
|
+
)
|
|
122
|
+
] });
|
|
123
|
+
});
|
|
124
|
+
PasswordInput.displayName = "PasswordInput";
|
|
125
|
+
const labelVariants = cva(
|
|
126
|
+
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
127
|
+
);
|
|
128
|
+
const Label = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(LabelPrimitive.Root, { ref, className: cn(labelVariants(), className), ...props }));
|
|
129
|
+
Label.displayName = LabelPrimitive.Root.displayName;
|
|
130
|
+
const Form = FormProvider;
|
|
131
|
+
const FormFieldContext = React.createContext(null);
|
|
132
|
+
const FormField = ({
|
|
133
|
+
...props
|
|
134
|
+
}) => {
|
|
135
|
+
return /* @__PURE__ */ jsx(FormFieldContext.Provider, { value: { name: props.name }, children: /* @__PURE__ */ jsx(Controller, { ...props }) });
|
|
136
|
+
};
|
|
137
|
+
const useFormField = () => {
|
|
138
|
+
const fieldContext = React.useContext(FormFieldContext);
|
|
139
|
+
const itemContext = React.useContext(FormItemContext);
|
|
140
|
+
const { getFieldState, formState } = useFormContext();
|
|
141
|
+
if (!fieldContext) {
|
|
142
|
+
throw new Error("useFormField should be used within <FormField>");
|
|
143
|
+
}
|
|
144
|
+
if (!itemContext) {
|
|
145
|
+
throw new Error("useFormField should be used within <FormItem>");
|
|
146
|
+
}
|
|
147
|
+
const fieldState = getFieldState(fieldContext.name, formState);
|
|
148
|
+
const { id } = itemContext;
|
|
149
|
+
return {
|
|
150
|
+
id,
|
|
151
|
+
name: fieldContext.name,
|
|
152
|
+
formItemId: `${id}-form-item`,
|
|
153
|
+
formDescriptionId: `${id}-form-item-description`,
|
|
154
|
+
formMessageId: `${id}-form-item-message`,
|
|
155
|
+
...fieldState
|
|
156
|
+
};
|
|
157
|
+
};
|
|
158
|
+
const FormItemContext = React.createContext(null);
|
|
159
|
+
const FormItem = React.forwardRef(
|
|
160
|
+
({ className, ...props }, ref) => {
|
|
161
|
+
const id = React.useId();
|
|
162
|
+
return /* @__PURE__ */ jsx(FormItemContext.Provider, { value: { id }, children: /* @__PURE__ */ jsx("div", { ref, className: cn("space-y-2", className), ...props }) });
|
|
163
|
+
}
|
|
164
|
+
);
|
|
165
|
+
FormItem.displayName = "FormItem";
|
|
166
|
+
const FormLabel = React.forwardRef(({ className, ...props }, ref) => {
|
|
167
|
+
const { error, formItemId } = useFormField();
|
|
168
|
+
return /* @__PURE__ */ jsx(
|
|
169
|
+
Label,
|
|
170
|
+
{
|
|
171
|
+
ref,
|
|
172
|
+
className: cn(error && "text-destructive", className),
|
|
173
|
+
htmlFor: formItemId,
|
|
174
|
+
...props
|
|
175
|
+
}
|
|
176
|
+
);
|
|
177
|
+
});
|
|
178
|
+
FormLabel.displayName = "FormLabel";
|
|
179
|
+
const FormControl = React.forwardRef(({ ...props }, ref) => {
|
|
180
|
+
const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
|
|
181
|
+
return /* @__PURE__ */ jsx(
|
|
182
|
+
Slot,
|
|
183
|
+
{
|
|
184
|
+
ref,
|
|
185
|
+
id: formItemId,
|
|
186
|
+
"aria-describedby": !error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`,
|
|
187
|
+
"aria-invalid": !!error,
|
|
188
|
+
...props
|
|
189
|
+
}
|
|
190
|
+
);
|
|
191
|
+
});
|
|
192
|
+
FormControl.displayName = "FormControl";
|
|
193
|
+
const FormDescription = React.forwardRef(({ className, ...props }, ref) => {
|
|
194
|
+
const { formDescriptionId } = useFormField();
|
|
195
|
+
return /* @__PURE__ */ jsx(
|
|
196
|
+
"p",
|
|
197
|
+
{
|
|
198
|
+
ref,
|
|
199
|
+
id: formDescriptionId,
|
|
200
|
+
className: cn("text-[0.8rem] text-muted-foreground", className),
|
|
201
|
+
...props
|
|
202
|
+
}
|
|
203
|
+
);
|
|
204
|
+
});
|
|
205
|
+
FormDescription.displayName = "FormDescription";
|
|
206
|
+
const FormMessage = React.forwardRef(({ className, children, ...props }, ref) => {
|
|
207
|
+
const { error, formMessageId } = useFormField();
|
|
208
|
+
const body = error ? String(error?.message ?? "") : children;
|
|
209
|
+
if (!body) {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
return /* @__PURE__ */ jsx(
|
|
213
|
+
"p",
|
|
214
|
+
{
|
|
215
|
+
ref,
|
|
216
|
+
id: formMessageId,
|
|
217
|
+
className: cn("text-[0.8rem] font-medium text-destructive", className),
|
|
218
|
+
...props,
|
|
219
|
+
children: body
|
|
220
|
+
}
|
|
221
|
+
);
|
|
222
|
+
});
|
|
223
|
+
FormMessage.displayName = "FormMessage";
|
|
224
|
+
export {
|
|
225
|
+
BrandMark as B,
|
|
226
|
+
Form as F,
|
|
227
|
+
Input as I,
|
|
228
|
+
PasswordInput as P,
|
|
229
|
+
TrustIndicators as T,
|
|
230
|
+
FormField as a,
|
|
231
|
+
FormItem as b,
|
|
232
|
+
FormLabel as c,
|
|
233
|
+
FormControl as d,
|
|
234
|
+
FormMessage as e,
|
|
235
|
+
Button as f
|
|
236
|
+
};
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import { jsxs, jsx, Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useState, useCallback, useEffect, Suspense, lazy } from "react";
|
|
3
|
+
import { AlertCircle, UploadCloud, Presentation, Sheet, FileText, FileWarning, ChevronDown, X, Loader2 } from "lucide-react";
|
|
4
|
+
import { L as LogoIcon } from "./LogoIcon-Dx0LU3or.js";
|
|
5
|
+
function FileDropzone({
|
|
6
|
+
onFile,
|
|
7
|
+
config
|
|
8
|
+
}) {
|
|
9
|
+
const inputRef = useRef(null);
|
|
10
|
+
const [dragging, setDragging] = useState(false);
|
|
11
|
+
const [wrongType, setWrongType] = useState(false);
|
|
12
|
+
const handleFiles = useCallback(
|
|
13
|
+
(files) => {
|
|
14
|
+
const file = files?.[0];
|
|
15
|
+
if (!file) return;
|
|
16
|
+
const ext = "." + (file.name.split(".").pop()?.toLowerCase() ?? "");
|
|
17
|
+
const accepted = config.accept.split(",").map((s) => s.trim());
|
|
18
|
+
if (!accepted.includes(ext)) {
|
|
19
|
+
setWrongType(true);
|
|
20
|
+
setTimeout(() => setWrongType(false), 3e3);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
onFile(file);
|
|
24
|
+
},
|
|
25
|
+
[onFile, config.accept]
|
|
26
|
+
);
|
|
27
|
+
const { icon: Icon, iconStyle, heading, description, dropHint, subtitle } = config;
|
|
28
|
+
return /* @__PURE__ */ jsxs("div", { className: "mx-auto flex w-full max-w-2xl flex-col items-center px-6", children: [
|
|
29
|
+
/* @__PURE__ */ jsx("div", { className: "mb-8", children: /* @__PURE__ */ jsx(
|
|
30
|
+
"div",
|
|
31
|
+
{
|
|
32
|
+
className: "flex h-18 w-18 items-center justify-center rounded-2xl",
|
|
33
|
+
style: iconStyle,
|
|
34
|
+
children: /* @__PURE__ */ jsx(Icon, { className: "h-9 w-9 text-white", strokeWidth: 1.5 })
|
|
35
|
+
}
|
|
36
|
+
) }),
|
|
37
|
+
/* @__PURE__ */ jsx("h1", { className: "text-center text-3xl font-bold tracking-tight text-foreground sm:text-4xl", children: heading }),
|
|
38
|
+
/* @__PURE__ */ jsx("p", { className: "mt-3 max-w-md text-center text-muted-foreground", children: description }),
|
|
39
|
+
/* @__PURE__ */ jsxs(
|
|
40
|
+
"button",
|
|
41
|
+
{
|
|
42
|
+
type: "button",
|
|
43
|
+
onClick: () => inputRef.current?.click(),
|
|
44
|
+
onDragOver: (e) => {
|
|
45
|
+
e.preventDefault();
|
|
46
|
+
setDragging(true);
|
|
47
|
+
},
|
|
48
|
+
onDragLeave: () => setDragging(false),
|
|
49
|
+
onDrop: (e) => {
|
|
50
|
+
e.preventDefault();
|
|
51
|
+
setDragging(false);
|
|
52
|
+
handleFiles(e.dataTransfer.files);
|
|
53
|
+
},
|
|
54
|
+
className: `mt-10 flex w-full flex-col items-center justify-center gap-4 rounded-2xl border-2 border-dashed px-8 py-16 text-center transition-colors ${wrongType ? "border-red-300 bg-red-50/70" : dragging ? "border-primary bg-accent" : "border-page-border bg-page hover:border-primary/50"}`,
|
|
55
|
+
children: [
|
|
56
|
+
wrongType ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
57
|
+
/* @__PURE__ */ jsx(AlertCircle, { className: "h-10 w-10 text-red-400" }),
|
|
58
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
59
|
+
/* @__PURE__ */ jsx("p", { className: "font-medium text-red-600", children: "Wrong file type" }),
|
|
60
|
+
/* @__PURE__ */ jsxs("p", { className: "mt-1 text-sm text-red-400", children: [
|
|
61
|
+
"This section only accepts ",
|
|
62
|
+
config.accept,
|
|
63
|
+
" files"
|
|
64
|
+
] })
|
|
65
|
+
] })
|
|
66
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
67
|
+
/* @__PURE__ */ jsx(UploadCloud, { className: "h-10 w-10 text-muted-foreground" }),
|
|
68
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
69
|
+
/* @__PURE__ */ jsx("p", { className: "font-medium text-foreground", children: dropHint }),
|
|
70
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: subtitle })
|
|
71
|
+
] })
|
|
72
|
+
] }),
|
|
73
|
+
/* @__PURE__ */ jsx(
|
|
74
|
+
"input",
|
|
75
|
+
{
|
|
76
|
+
ref: inputRef,
|
|
77
|
+
type: "file",
|
|
78
|
+
accept: config.accept,
|
|
79
|
+
className: "hidden",
|
|
80
|
+
onChange: (e) => handleFiles(e.target.files)
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
)
|
|
86
|
+
] });
|
|
87
|
+
}
|
|
88
|
+
const DocxViewer = lazy(() => import("./DocxViewer-Bm8UJY-7.js").then((m) => ({
|
|
89
|
+
default: m.DocxViewer
|
|
90
|
+
})));
|
|
91
|
+
const XlsxViewer = lazy(() => import("./XlsxViewer-jzIgKmN2.js").then((m) => ({
|
|
92
|
+
default: m.XlsxViewer
|
|
93
|
+
})));
|
|
94
|
+
const PptxViewer = lazy(() => import("./PptxViewer-DS7Atucw.js").then((m) => ({
|
|
95
|
+
default: m.PptxViewer
|
|
96
|
+
})));
|
|
97
|
+
function detectKind(name) {
|
|
98
|
+
const ext = name.split(".").pop()?.toLowerCase();
|
|
99
|
+
if (ext === "docx") return "docx";
|
|
100
|
+
if (ext === "xlsx") return "xlsx";
|
|
101
|
+
if (ext === "pptx") return "pptx";
|
|
102
|
+
return "unsupported";
|
|
103
|
+
}
|
|
104
|
+
const KIND_META = {
|
|
105
|
+
docx: {
|
|
106
|
+
label: "Word Document",
|
|
107
|
+
icon: FileText,
|
|
108
|
+
cls: "bg-word text-word-foreground"
|
|
109
|
+
},
|
|
110
|
+
xlsx: {
|
|
111
|
+
label: "Excel Spreadsheet",
|
|
112
|
+
icon: Sheet,
|
|
113
|
+
cls: "bg-excel text-excel-foreground"
|
|
114
|
+
},
|
|
115
|
+
pptx: {
|
|
116
|
+
label: "PowerPoint Presentation",
|
|
117
|
+
icon: Presentation,
|
|
118
|
+
cls: "bg-powerpoint text-powerpoint-foreground"
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
const navItems = [{
|
|
122
|
+
id: "docs",
|
|
123
|
+
label: "Docs Viewer",
|
|
124
|
+
icon: FileText,
|
|
125
|
+
color: "text-slate-400",
|
|
126
|
+
activeColor: "text-blue-600",
|
|
127
|
+
accentColor: "bg-blue-600",
|
|
128
|
+
activeBg: "bg-blue-50"
|
|
129
|
+
}, {
|
|
130
|
+
id: "excel",
|
|
131
|
+
label: "Excel Viewer",
|
|
132
|
+
icon: Sheet,
|
|
133
|
+
color: "text-slate-400",
|
|
134
|
+
activeColor: "text-emerald-600",
|
|
135
|
+
accentColor: "bg-emerald-500",
|
|
136
|
+
activeBg: "bg-emerald-50"
|
|
137
|
+
}, {
|
|
138
|
+
id: "ppt",
|
|
139
|
+
label: "PPT Viewer",
|
|
140
|
+
icon: Presentation,
|
|
141
|
+
color: "text-slate-400",
|
|
142
|
+
activeColor: "text-orange-500",
|
|
143
|
+
accentColor: "bg-orange-400",
|
|
144
|
+
activeBg: "bg-orange-50"
|
|
145
|
+
}];
|
|
146
|
+
const kindToView = {
|
|
147
|
+
docx: "docs",
|
|
148
|
+
xlsx: "excel",
|
|
149
|
+
pptx: "ppt"
|
|
150
|
+
};
|
|
151
|
+
const VIEW_CONFIG = {
|
|
152
|
+
docs: {
|
|
153
|
+
accept: ".docx",
|
|
154
|
+
icon: FileText,
|
|
155
|
+
iconStyle: {
|
|
156
|
+
background: "linear-gradient(145deg, #1e40af 0%, #2563eb 55%, #93c5fd 100%)",
|
|
157
|
+
boxShadow: "0 0 0 1px rgba(37,99,235,0.18), inset 0 1px 0 rgba(255,255,255,0.18), 0 4px 20px rgba(37,99,235,0.38)"
|
|
158
|
+
},
|
|
159
|
+
heading: "Word Document Viewer",
|
|
160
|
+
description: "Open Word documents directly in your browser — text, tables, images and charts rendered locally. Nothing leaves your device.",
|
|
161
|
+
dropHint: "Drop your .docx file here, or click to browse",
|
|
162
|
+
subtitle: "Accepts .docx files only"
|
|
163
|
+
},
|
|
164
|
+
excel: {
|
|
165
|
+
accept: ".xlsx",
|
|
166
|
+
icon: Sheet,
|
|
167
|
+
iconStyle: {
|
|
168
|
+
background: "linear-gradient(145deg, #166534 0%, #16a34a 55%, #86efac 100%)",
|
|
169
|
+
boxShadow: "0 0 0 1px rgba(22,163,74,0.18), inset 0 1px 0 rgba(255,255,255,0.18), 0 4px 20px rgba(22,163,74,0.38)"
|
|
170
|
+
},
|
|
171
|
+
heading: "Excel Spreadsheet Viewer",
|
|
172
|
+
description: "Open Excel spreadsheets directly in your browser — cell styles, merged cells and formulas rendered locally. Nothing leaves your device.",
|
|
173
|
+
dropHint: "Drop your .xlsx file here, or click to browse",
|
|
174
|
+
subtitle: "Accepts .xlsx files only"
|
|
175
|
+
},
|
|
176
|
+
ppt: {
|
|
177
|
+
accept: ".pptx",
|
|
178
|
+
icon: Presentation,
|
|
179
|
+
iconStyle: {
|
|
180
|
+
background: "linear-gradient(145deg, #9a3412 0%, #ea580c 55%, #fdba74 100%)",
|
|
181
|
+
boxShadow: "0 0 0 1px rgba(234,88,12,0.18), inset 0 1px 0 rgba(255,255,255,0.18), 0 4px 20px rgba(234,88,12,0.38)"
|
|
182
|
+
},
|
|
183
|
+
heading: "PowerPoint Viewer",
|
|
184
|
+
description: "Open PowerPoint presentations directly in your browser — all slides, layouts and charts rendered locally. Nothing leaves your device.",
|
|
185
|
+
dropHint: "Drop your .pptx file here, or click to browse",
|
|
186
|
+
subtitle: "Accepts .pptx files only"
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
const SIDEBAR_COLLAPSED = 72;
|
|
190
|
+
const SIDEBAR_EXPANDED = 264;
|
|
191
|
+
const ICON_INSET = 24;
|
|
192
|
+
const EASE = "cubic-bezier(0.4, 0, 0.2, 1)";
|
|
193
|
+
const SIDEBAR_TR = `width 300ms ${EASE}, box-shadow 300ms ${EASE}`;
|
|
194
|
+
const LABEL_TR = "opacity 180ms ease, max-width 300ms ease, margin-left 280ms ease";
|
|
195
|
+
function Index() {
|
|
196
|
+
const [doc, setDoc] = useState(null);
|
|
197
|
+
const [mounted, setMounted] = useState(false);
|
|
198
|
+
const [activeView, setActiveView] = useState("docs");
|
|
199
|
+
const [expanded, setExpanded] = useState(false);
|
|
200
|
+
const [pptMode, setPptMode] = useState("scroll");
|
|
201
|
+
const [docMode, setDocMode] = useState("scroll");
|
|
202
|
+
useEffect(() => setMounted(true), []);
|
|
203
|
+
const openFile = async (file) => {
|
|
204
|
+
const data = await file.arrayBuffer();
|
|
205
|
+
const kind = detectKind(file.name);
|
|
206
|
+
setDoc({
|
|
207
|
+
name: file.name,
|
|
208
|
+
kind,
|
|
209
|
+
data
|
|
210
|
+
});
|
|
211
|
+
if (kind !== "unsupported") setActiveView(kindToView[kind]);
|
|
212
|
+
};
|
|
213
|
+
const closeDoc = () => setDoc(null);
|
|
214
|
+
const meta = doc && doc.kind !== "unsupported" ? KIND_META[doc.kind] : null;
|
|
215
|
+
const DocIcon = meta?.icon ?? FileWarning;
|
|
216
|
+
const sidebarW = expanded ? SIDEBAR_EXPANDED : SIDEBAR_COLLAPSED;
|
|
217
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex h-screen flex-col bg-slate-50", children: [
|
|
218
|
+
/* @__PURE__ */ jsxs("header", { className: "fixed inset-x-0 top-0 z-30 flex h-[72px] items-center justify-between border-b border-slate-200/70 bg-white px-8", style: {
|
|
219
|
+
boxShadow: "0 1px 4px 0 rgba(15,23,42,0.07)"
|
|
220
|
+
}, children: [
|
|
221
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3.5", children: [
|
|
222
|
+
/* @__PURE__ */ jsx("div", { className: "flex h-[48px] w-[48px] shrink-0 items-center justify-center rounded-[14px]", style: {
|
|
223
|
+
backgroundColor: "#2563eb",
|
|
224
|
+
boxShadow: "0 0 0 1px rgba(37,99,235,0.22), inset 0 1px 0 rgba(255,255,255,0.15), 0 6px 22px 0 rgba(37,99,235,0.30)"
|
|
225
|
+
}, children: /* @__PURE__ */ jsx(LogoIcon, { size: 24 }) }),
|
|
226
|
+
/* @__PURE__ */ jsxs("div", { className: "flex select-none flex-col justify-center", style: {
|
|
227
|
+
gap: 1
|
|
228
|
+
}, children: [
|
|
229
|
+
/* @__PURE__ */ jsx("span", { className: "text-[11px] font-semibold uppercase tracking-[0.22em] text-blue-600", children: "Office" }),
|
|
230
|
+
/* @__PURE__ */ jsx("span", { className: "text-[24px] font-bold leading-none tracking-[-0.03em] text-slate-900", children: "Viewer" })
|
|
231
|
+
] })
|
|
232
|
+
] }),
|
|
233
|
+
(activeView === "ppt" || activeView === "docs" || doc) && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
234
|
+
activeView === "docs" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
235
|
+
/* @__PURE__ */ jsx("span", { className: "select-none text-[12.5px] font-medium text-slate-400", children: "View Mode" }),
|
|
236
|
+
/* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
237
|
+
/* @__PURE__ */ jsxs("select", { value: docMode, onChange: (e) => setDocMode(e.target.value), className: "appearance-none cursor-pointer rounded-lg border border-slate-200 bg-white py-2 pl-3.5 pr-8 text-[13px] font-medium text-slate-700 shadow-sm transition-colors hover:border-slate-300 focus:border-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-400/20", children: [
|
|
238
|
+
/* @__PURE__ */ jsx("option", { value: "scroll", children: "Continuous Scroll" }),
|
|
239
|
+
/* @__PURE__ */ jsx("option", { value: "navigation", children: "Page Navigation" })
|
|
240
|
+
] }),
|
|
241
|
+
/* @__PURE__ */ jsx(ChevronDown, { className: "pointer-events-none absolute right-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-slate-400" })
|
|
242
|
+
] })
|
|
243
|
+
] }),
|
|
244
|
+
activeView === "ppt" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
245
|
+
/* @__PURE__ */ jsx("span", { className: "select-none text-[12.5px] font-medium text-slate-400", children: "View Mode" }),
|
|
246
|
+
/* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
247
|
+
/* @__PURE__ */ jsxs("select", { value: pptMode, onChange: (e) => setPptMode(e.target.value), className: "appearance-none cursor-pointer rounded-lg border border-slate-200 bg-white py-2 pl-3.5 pr-8 text-[13px] font-medium text-slate-700 shadow-sm transition-colors hover:border-slate-300 focus:border-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-400/20", children: [
|
|
248
|
+
/* @__PURE__ */ jsx("option", { value: "scroll", children: "Vertical Scroll" }),
|
|
249
|
+
/* @__PURE__ */ jsx("option", { value: "navigation", children: "Slide Navigation" })
|
|
250
|
+
] }),
|
|
251
|
+
/* @__PURE__ */ jsx(ChevronDown, { className: "pointer-events-none absolute right-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-slate-400" })
|
|
252
|
+
] })
|
|
253
|
+
] }),
|
|
254
|
+
(activeView === "ppt" || activeView === "docs") && doc && /* @__PURE__ */ jsx("div", { className: "h-5 w-px shrink-0 bg-slate-200" }),
|
|
255
|
+
doc && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
256
|
+
/* @__PURE__ */ jsx("span", { className: `flex h-8.5 w-8.5 shrink-0 items-center justify-center rounded-xl ${meta?.cls ?? "bg-slate-100 text-slate-500"}`, children: /* @__PURE__ */ jsx(DocIcon, { className: "h-4.25 w-4.25" }) }),
|
|
257
|
+
/* @__PURE__ */ jsx("span", { className: "max-w-90 truncate text-[14px] font-medium text-slate-600", children: doc.name }),
|
|
258
|
+
/* @__PURE__ */ jsx("div", { className: "mx-1 h-5 w-px shrink-0 bg-slate-200" }),
|
|
259
|
+
/* @__PURE__ */ jsxs("button", { onClick: closeDoc, className: "inline-flex items-center gap-1.5 rounded-xl border border-slate-200 bg-white px-4 py-1.75 text-[13.5px] font-medium text-slate-600 transition-all duration-150 hover:border-slate-300 hover:bg-slate-50 hover:text-slate-900", children: [
|
|
260
|
+
/* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" }),
|
|
261
|
+
"Close file"
|
|
262
|
+
] })
|
|
263
|
+
] })
|
|
264
|
+
] })
|
|
265
|
+
] }),
|
|
266
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-1 overflow-hidden pt-[72px]", children: [
|
|
267
|
+
/* @__PURE__ */ jsxs("aside", { onMouseEnter: () => setExpanded(true), onMouseLeave: () => setExpanded(false), style: {
|
|
268
|
+
width: sidebarW,
|
|
269
|
+
transition: SIDEBAR_TR,
|
|
270
|
+
boxShadow: expanded ? "3px 0 28px 0 rgba(15,23,42,0.11)" : "3px 0 0 0 rgba(15,23,42,0)"
|
|
271
|
+
}, className: "fixed bottom-0 left-0 top-[72px] z-[25] flex flex-col overflow-hidden border-r border-slate-200/70 bg-white", children: [
|
|
272
|
+
/* @__PURE__ */ jsx("div", { className: "shrink-0 px-3 pb-2 pt-8", style: {
|
|
273
|
+
minHeight: 52
|
|
274
|
+
}, children: /* @__PURE__ */ jsx("p", { className: "select-none whitespace-nowrap pl-0.75 text-[10.5px] font-semibold uppercase tracking-[0.11em] text-slate-400", style: {
|
|
275
|
+
opacity: expanded ? 1 : 0,
|
|
276
|
+
transition: "opacity 200ms ease"
|
|
277
|
+
}, children: "Viewers" }) }),
|
|
278
|
+
/* @__PURE__ */ jsx("nav", { className: "flex flex-col gap-1", children: navItems.map(({
|
|
279
|
+
id,
|
|
280
|
+
label,
|
|
281
|
+
icon: Icon,
|
|
282
|
+
color,
|
|
283
|
+
activeColor,
|
|
284
|
+
accentColor,
|
|
285
|
+
activeBg
|
|
286
|
+
}) => {
|
|
287
|
+
const isActive = activeView === id;
|
|
288
|
+
const chipBg = isActive ? activeBg : !expanded ? "group-hover:bg-slate-100" : "";
|
|
289
|
+
return /* @__PURE__ */ jsxs("div", { className: "group relative", children: [
|
|
290
|
+
isActive && /* @__PURE__ */ jsx("div", { className: `absolute bottom-1.5 left-0 top-1.5 w-0.5 rounded-r-full ${accentColor}` }),
|
|
291
|
+
/* @__PURE__ */ jsxs("button", { onClick: () => {
|
|
292
|
+
setActiveView(id);
|
|
293
|
+
if (doc && doc.kind !== "unsupported" && kindToView[doc.kind] !== id) closeDoc();
|
|
294
|
+
}, style: {
|
|
295
|
+
// Collapsed: centre the 40 px chip in the 72 px rail (16 px each side).
|
|
296
|
+
// Expanded: restore the original left inset so the icon aligns with the header logo.
|
|
297
|
+
paddingLeft: expanded ? ICON_INSET : (SIDEBAR_COLLAPSED - 40) / 2,
|
|
298
|
+
paddingRight: expanded ? 8 : (SIDEBAR_COLLAPSED - 40) / 2
|
|
299
|
+
}, className: `relative flex w-full items-center rounded-md transition-colors duration-150 ${expanded ? "py-3.5" : "py-3"} ${isActive ? activeColor : `${color} hover:text-slate-600 ${expanded ? "hover:bg-slate-50" : ""}`}`, children: [
|
|
300
|
+
/* @__PURE__ */ jsx("div", { className: `flex h-10 w-10 shrink-0 items-center justify-center rounded-xl transition-colors duration-200 ${chipBg}`, children: /* @__PURE__ */ jsx(Icon, { className: "h-5 w-5 shrink-0", strokeWidth: isActive ? 2.25 : 1.75 }) }),
|
|
301
|
+
/* @__PURE__ */ jsx("span", { style: {
|
|
302
|
+
maxWidth: expanded ? 160 : 0,
|
|
303
|
+
opacity: expanded ? 1 : 0,
|
|
304
|
+
marginLeft: expanded ? 12 : 0,
|
|
305
|
+
overflow: "hidden",
|
|
306
|
+
whiteSpace: "nowrap",
|
|
307
|
+
transition: LABEL_TR
|
|
308
|
+
}, className: "text-[14.5px] font-medium tracking-[-0.01em]", children: label })
|
|
309
|
+
] }),
|
|
310
|
+
!expanded && /* @__PURE__ */ jsxs("div", { className: "pointer-events-none absolute left-full top-1/2 z-50 ml-3 -translate-y-1/2 whitespace-nowrap rounded-lg bg-slate-900 px-3 py-1.75 text-[13px] font-medium text-white opacity-0 shadow-xl transition-opacity duration-150 group-hover:opacity-100", children: [
|
|
311
|
+
label,
|
|
312
|
+
/* @__PURE__ */ jsx("span", { className: "absolute right-full top-1/2 -translate-y-1/2 border-4 border-transparent border-r-slate-900" })
|
|
313
|
+
] })
|
|
314
|
+
] }, id);
|
|
315
|
+
}) }),
|
|
316
|
+
/* @__PURE__ */ jsx("div", { className: "mt-auto border-t border-slate-100 px-[22px] py-5", style: {
|
|
317
|
+
opacity: expanded ? 1 : 0,
|
|
318
|
+
transition: "opacity 200ms ease"
|
|
319
|
+
}, children: /* @__PURE__ */ jsx("p", { className: "whitespace-nowrap text-[12px] leading-relaxed text-slate-400", children: ".docx · .xlsx · .pptx" }) })
|
|
320
|
+
] }),
|
|
321
|
+
/* @__PURE__ */ jsxs("div", { style: {
|
|
322
|
+
marginLeft: SIDEBAR_COLLAPSED
|
|
323
|
+
}, className: "flex flex-1 flex-col overflow-hidden", children: [
|
|
324
|
+
/* @__PURE__ */ jsxs("main", { className: `flex-1 ${doc?.kind === "pptx" && pptMode === "navigation" ? "overflow-hidden" : "overflow-auto"}`, children: [
|
|
325
|
+
!doc && /* @__PURE__ */ jsx("div", { className: "flex min-h-full items-center justify-center px-8 py-16", children: /* @__PURE__ */ jsx("div", { className: "w-full max-w-150", children: /* @__PURE__ */ jsx(FileDropzone, { onFile: openFile, config: VIEW_CONFIG[activeView] }, activeView) }) }),
|
|
326
|
+
doc && !mounted && /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsx(Loader2, { className: "h-8 w-8 animate-spin text-slate-300" }) }),
|
|
327
|
+
doc && mounted && doc.kind === "unsupported" && /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center p-12", children: /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-md rounded-2xl border border-slate-200 bg-white px-12 py-12 text-center", style: {
|
|
328
|
+
boxShadow: "0 1px 4px 0 rgba(15,23,42,0.06)"
|
|
329
|
+
}, children: [
|
|
330
|
+
/* @__PURE__ */ jsx(FileWarning, { className: "mx-auto h-12 w-12 text-slate-300" }),
|
|
331
|
+
/* @__PURE__ */ jsx("h2", { className: "mt-6 text-[20px] font-semibold tracking-tight text-slate-800", children: "Unsupported file type" }),
|
|
332
|
+
/* @__PURE__ */ jsxs("p", { className: "mt-3 text-[14.5px] leading-relaxed text-slate-500", children: [
|
|
333
|
+
"Please use a supported format:",
|
|
334
|
+
" ",
|
|
335
|
+
/* @__PURE__ */ jsx("strong", { className: "font-semibold text-slate-700", children: ".docx" }),
|
|
336
|
+
",",
|
|
337
|
+
" ",
|
|
338
|
+
/* @__PURE__ */ jsx("strong", { className: "font-semibold text-slate-700", children: ".xlsx" }),
|
|
339
|
+
" or",
|
|
340
|
+
" ",
|
|
341
|
+
/* @__PURE__ */ jsx("strong", { className: "font-semibold text-slate-700", children: ".pptx" }),
|
|
342
|
+
".",
|
|
343
|
+
/* @__PURE__ */ jsx("br", {}),
|
|
344
|
+
"Legacy formats (.doc, .xls, .ppt) are not supported."
|
|
345
|
+
] }),
|
|
346
|
+
/* @__PURE__ */ jsx("button", { onClick: closeDoc, className: "mt-8 rounded-xl bg-blue-600 px-7 py-[10px] text-[14px] font-semibold text-white transition-colors duration-150 hover:bg-blue-700", children: "Try another file" })
|
|
347
|
+
] }) }),
|
|
348
|
+
doc && mounted && doc.kind !== "unsupported" && /* @__PURE__ */ jsxs(Suspense, { fallback: /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsx(Loader2, { className: "h-8 w-8 animate-spin text-slate-300" }) }), children: [
|
|
349
|
+
doc.kind === "docx" && /* @__PURE__ */ jsx(DocxViewer, { data: doc.data, mode: docMode }),
|
|
350
|
+
doc.kind === "xlsx" && /* @__PURE__ */ jsx(XlsxViewer, { data: doc.data }),
|
|
351
|
+
doc.kind === "pptx" && /* @__PURE__ */ jsx(PptxViewer, { data: doc.data, mode: pptMode })
|
|
352
|
+
] })
|
|
353
|
+
] }),
|
|
354
|
+
/* @__PURE__ */ jsx("footer", { className: "shrink-0 border-t border-slate-200/70 bg-white px-8 py-3", children: /* @__PURE__ */ jsxs("p", { className: "text-center text-[12px] text-slate-400", children: [
|
|
355
|
+
"Powered by",
|
|
356
|
+
" ",
|
|
357
|
+
/* @__PURE__ */ jsx("span", { className: "font-semibold text-slate-500", children: "Virtualan Software" })
|
|
358
|
+
] }) })
|
|
359
|
+
] })
|
|
360
|
+
] })
|
|
361
|
+
] });
|
|
362
|
+
}
|
|
363
|
+
export {
|
|
364
|
+
Index as component
|
|
365
|
+
};
|