@arch-cadre/panel 0.0.1
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/dist/actions/actions.cjs +18 -0
- package/dist/actions/actions.d.ts +4 -0
- package/dist/actions/actions.mjs +10 -0
- package/dist/actions/index.cjs +27 -0
- package/dist/actions/index.d.ts +2 -0
- package/dist/actions/index.mjs +2 -0
- package/dist/actions/manager.cjs +138 -0
- package/dist/actions/manager.d.ts +14 -0
- package/dist/actions/manager.mjs +135 -0
- package/dist/actions/profile.cjs +169 -0
- package/dist/actions/profile.d.ts +8 -0
- package/dist/actions/profile.mjs +135 -0
- package/dist/actions/settings.cjs +26 -0
- package/dist/actions/settings.d.ts +7 -0
- package/dist/actions/settings.mjs +20 -0
- package/dist/index.cjs +29 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.mjs +25 -0
- package/dist/intl.d.ts +9 -0
- package/dist/navigation.cjs +38 -0
- package/dist/navigation.d.ts +2 -0
- package/dist/navigation.mjs +39 -0
- package/dist/routes.cjs +71 -0
- package/dist/routes.d.ts +4 -0
- package/dist/routes.mjs +71 -0
- package/dist/types.cjs +1 -0
- package/dist/types.d.ts +13 -0
- package/dist/types.mjs +0 -0
- package/dist/ui/[...catchAll]/page.cjs +127 -0
- package/dist/ui/[...catchAll]/page.d.ts +13 -0
- package/dist/ui/[...catchAll]/page.mjs +88 -0
- package/dist/ui/components/app-content.cjs +49 -0
- package/dist/ui/components/app-content.d.ts +6 -0
- package/dist/ui/components/app-content.mjs +34 -0
- package/dist/ui/components/app-header.cjs +55 -0
- package/dist/ui/components/app-header.d.ts +6 -0
- package/dist/ui/components/app-header.mjs +45 -0
- package/dist/ui/components/app-sidebar.cjs +133 -0
- package/dist/ui/components/app-sidebar.d.ts +17 -0
- package/dist/ui/components/app-sidebar.mjs +142 -0
- package/dist/ui/components/app-user.cjs +102 -0
- package/dist/ui/components/app-user.d.ts +2 -0
- package/dist/ui/components/app-user.mjs +92 -0
- package/dist/ui/components/breadcrumb-slot.cjs +35 -0
- package/dist/ui/components/breadcrumb-slot.d.ts +2 -0
- package/dist/ui/components/breadcrumb-slot.mjs +31 -0
- package/dist/ui/components/manager/module-card.cjs +213 -0
- package/dist/ui/components/manager/module-card.d.ts +12 -0
- package/dist/ui/components/manager/module-card.mjs +197 -0
- package/dist/ui/components/manager/module-list.cjs +52 -0
- package/dist/ui/components/manager/module-list.d.ts +4 -0
- package/dist/ui/components/manager/module-list.mjs +16 -0
- package/dist/ui/components/manager/module-upload.cjs +81 -0
- package/dist/ui/components/manager/module-upload.d.ts +2 -0
- package/dist/ui/components/manager/module-upload.mjs +68 -0
- package/dist/ui/components/profile/components.cjs +239 -0
- package/dist/ui/components/profile/components.d.ts +8 -0
- package/dist/ui/components/profile/components.mjs +219 -0
- package/dist/ui/components/profile/link.cjs +25 -0
- package/dist/ui/components/profile/link.d.ts +1 -0
- package/dist/ui/components/profile/link.mjs +13 -0
- package/dist/ui/components/profile/page.cjs +41 -0
- package/dist/ui/components/profile/page.d.ts +1 -0
- package/dist/ui/components/profile/page.mjs +21 -0
- package/dist/ui/components/sidebar-slot.cjs +49 -0
- package/dist/ui/components/sidebar-slot.d.ts +2 -0
- package/dist/ui/components/sidebar-slot.mjs +33 -0
- package/dist/ui/dashboard/page.cjs +31 -0
- package/dist/ui/dashboard/page.d.ts +2 -0
- package/dist/ui/dashboard/page.mjs +9 -0
- package/dist/ui/error.cjs +50 -0
- package/dist/ui/error.d.ts +7 -0
- package/dist/ui/error.mjs +35 -0
- package/dist/ui/layout.cjs +48 -0
- package/dist/ui/layout.d.ts +4 -0
- package/dist/ui/layout.mjs +35 -0
- package/dist/ui/modules/docs/page.cjs +98 -0
- package/dist/ui/modules/docs/page.d.ts +6 -0
- package/dist/ui/modules/docs/page.mjs +46 -0
- package/dist/ui/modules/page.cjs +30 -0
- package/dist/ui/modules/page.d.ts +2 -0
- package/dist/ui/modules/page.mjs +10 -0
- package/dist/ui/page.cjs +18 -0
- package/dist/ui/page.d.ts +1 -0
- package/dist/ui/page.mjs +9 -0
- package/dist/ui/router.cjs +61 -0
- package/dist/ui/router.d.ts +5 -0
- package/dist/ui/router.mjs +51 -0
- package/dist/ui/settings/page.cjs +61 -0
- package/dist/ui/settings/page.d.ts +2 -0
- package/dist/ui/settings/page.mjs +22 -0
- package/dist/ui/settings-page.cjs +76 -0
- package/dist/ui/settings-page.d.ts +2 -0
- package/dist/ui/settings-page.mjs +57 -0
- package/locales/en/global.json +80 -0
- package/manifest.json +11 -0
- package/package.json +67 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useTranslation } from "@arch-cadre/intl";
|
|
3
|
+
import { Button } from "@arch-cadre/ui/components/button";
|
|
4
|
+
import { Loader2, Lock, Upload } from "lucide-react";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
import { useEffect, useRef, useState } from "react";
|
|
7
|
+
import { toast } from "sonner";
|
|
8
|
+
import {
|
|
9
|
+
checkDiskWriteAccess,
|
|
10
|
+
uploadModuleAction
|
|
11
|
+
} from "../../../actions/manager.mjs";
|
|
12
|
+
export function ModuleUpload() {
|
|
13
|
+
const [isUploading, setIsUploading] = useState(false);
|
|
14
|
+
const [canWrite, setCanWrite] = useState(null);
|
|
15
|
+
const fileInputRef = useRef(null);
|
|
16
|
+
const { t } = useTranslation();
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
checkDiskWriteAccess().then((res) => setCanWrite(res.canWrite));
|
|
19
|
+
}, []);
|
|
20
|
+
const handleUpload = async (e) => {
|
|
21
|
+
const file = e.target.files?.[0];
|
|
22
|
+
if (!file) return;
|
|
23
|
+
if (!file.name.endsWith(".zip")) {
|
|
24
|
+
toast.error(t("Invalid file"));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const formData = new FormData();
|
|
28
|
+
formData.append("file", file);
|
|
29
|
+
setIsUploading(true);
|
|
30
|
+
const id = toast.loading(t("Uploading..."));
|
|
31
|
+
try {
|
|
32
|
+
const result = await uploadModuleAction(formData);
|
|
33
|
+
if (result.success) {
|
|
34
|
+
toast.success(t("Upload successful"), { id });
|
|
35
|
+
if (fileInputRef.current) fileInputRef.current.value = "";
|
|
36
|
+
} else {
|
|
37
|
+
toast.error(result.error || t("Upload error"), { id });
|
|
38
|
+
}
|
|
39
|
+
} catch (_error) {
|
|
40
|
+
toast.error(t("error_occurred"), { id });
|
|
41
|
+
} finally {
|
|
42
|
+
setIsUploading(false);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
return /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-4" }, /* @__PURE__ */ React.createElement(
|
|
46
|
+
"input",
|
|
47
|
+
{
|
|
48
|
+
type: "file",
|
|
49
|
+
accept: ".zip",
|
|
50
|
+
className: "hidden",
|
|
51
|
+
ref: fileInputRef,
|
|
52
|
+
onChange: handleUpload,
|
|
53
|
+
disabled: isUploading
|
|
54
|
+
}
|
|
55
|
+
), /* @__PURE__ */ React.createElement(
|
|
56
|
+
Button,
|
|
57
|
+
{
|
|
58
|
+
onClick: () => fileInputRef.current?.click(),
|
|
59
|
+
disabled: isUploading || canWrite === false,
|
|
60
|
+
variant: "outline",
|
|
61
|
+
size: "sm",
|
|
62
|
+
className: "gap-2 w-full sm:w-auto",
|
|
63
|
+
title: canWrite === false ? t("No write access") : void 0
|
|
64
|
+
},
|
|
65
|
+
isUploading ? /* @__PURE__ */ React.createElement(Loader2, { className: "size-4 animate-spin" }) : canWrite === false ? /* @__PURE__ */ React.createElement(Lock, { className: "size-4 text-destructive" }) : /* @__PURE__ */ React.createElement(Upload, { className: "size-4" }),
|
|
66
|
+
canWrite === false ? t("Upload disabled") : t("Upload module")
|
|
67
|
+
));
|
|
68
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.UpdateEmailForm = UpdateEmailForm;
|
|
8
|
+
exports.UpdatePasswordForm = UpdatePasswordForm;
|
|
9
|
+
exports.UpdateProfileForm = UpdateProfileForm;
|
|
10
|
+
var _intl = require("@arch-cadre/intl");
|
|
11
|
+
var _ui = require("@arch-cadre/ui");
|
|
12
|
+
var _avatar = require("@arch-cadre/ui/components/avatar");
|
|
13
|
+
var _button = require("@arch-cadre/ui/components/button");
|
|
14
|
+
var _input = require("@arch-cadre/ui/components/input");
|
|
15
|
+
var _label = require("@arch-cadre/ui/components/label");
|
|
16
|
+
var _react = require("react");
|
|
17
|
+
var _profile = require("../../../actions/profile.cjs");
|
|
18
|
+
function UpdateProfileForm({
|
|
19
|
+
user
|
|
20
|
+
}) {
|
|
21
|
+
const {
|
|
22
|
+
t
|
|
23
|
+
} = (0, _intl.useTranslation)();
|
|
24
|
+
const [name, setName] = (0, _react.useState)(user.name);
|
|
25
|
+
const [isPending, startTransition] = (0, _react.useTransition)();
|
|
26
|
+
const [image, setImage] = (0, _react.useState)(user.image);
|
|
27
|
+
const [avatar, setAvatar] = (0, _react.useState)(void 0);
|
|
28
|
+
const fileInputRef = (0, _react.useRef)(null);
|
|
29
|
+
const handleFileChange = e => {
|
|
30
|
+
const file = e.target.files?.[0];
|
|
31
|
+
if (file) {
|
|
32
|
+
setAvatar(file);
|
|
33
|
+
const reader = new FileReader();
|
|
34
|
+
reader.onload = event => {
|
|
35
|
+
setImage(event.target?.result);
|
|
36
|
+
};
|
|
37
|
+
reader.readAsDataURL(file);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
const triggerFileInput = () => {
|
|
41
|
+
fileInputRef.current?.click();
|
|
42
|
+
};
|
|
43
|
+
const handleSubmit = async e => {
|
|
44
|
+
e.preventDefault();
|
|
45
|
+
if (!name.trim()) {
|
|
46
|
+
_ui.toast.error(t("Name is required"));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
startTransition(async () => {
|
|
50
|
+
const result = await (0, _profile.updateProfileAction)(name.trim(), avatar);
|
|
51
|
+
if (result.error) {
|
|
52
|
+
_ui.toast.error(result.error);
|
|
53
|
+
} else {
|
|
54
|
+
_ui.toast.success(result.message);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
return /* @__PURE__ */React.createElement("form", {
|
|
59
|
+
onSubmit: handleSubmit,
|
|
60
|
+
className: "space-y-4"
|
|
61
|
+
}, /* @__PURE__ */React.createElement("div", {
|
|
62
|
+
className: "grid grid-cols-12"
|
|
63
|
+
}, /* @__PURE__ */React.createElement("div", {
|
|
64
|
+
className: "col-span-3 flex items-center flex-col"
|
|
65
|
+
}, /* @__PURE__ */React.createElement("div", {
|
|
66
|
+
className: "relative mb-2"
|
|
67
|
+
}, /* @__PURE__ */React.createElement(_avatar.Avatar, {
|
|
68
|
+
className: "h-24 w-24 border-2 border-muted"
|
|
69
|
+
}, /* @__PURE__ */React.createElement(_avatar.AvatarImage, {
|
|
70
|
+
src: image || void 0,
|
|
71
|
+
alt: name
|
|
72
|
+
}), /* @__PURE__ */React.createElement(_avatar.AvatarFallback, {
|
|
73
|
+
className: "text-3xl"
|
|
74
|
+
}, name.charAt(0))), /* @__PURE__ */React.createElement(_button.Button, {
|
|
75
|
+
type: "button",
|
|
76
|
+
variant: "ghost",
|
|
77
|
+
size: "icon",
|
|
78
|
+
className: "absolute -top-0.5 -right-0.5 bg-accent rounded-full border-[3px] border-background h-8 w-8 hover:bg-accent",
|
|
79
|
+
onClick: () => {
|
|
80
|
+
if (image) {
|
|
81
|
+
setImage(null);
|
|
82
|
+
if (fileInputRef.current) {
|
|
83
|
+
fileInputRef.current.value = "";
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
triggerFileInput();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}, image ? /* @__PURE__ */React.createElement(_ui.Icon, {
|
|
90
|
+
icon: "solar:trash-bin-trash-broken",
|
|
91
|
+
className: "h-4 w-4 text-muted-foreground"
|
|
92
|
+
}) : /* @__PURE__ */React.createElement(_ui.Icon, {
|
|
93
|
+
icon: "solar:add-circle-broken",
|
|
94
|
+
className: "h-3 w-3 text-muted-foreground"
|
|
95
|
+
}), /* @__PURE__ */React.createElement("span", {
|
|
96
|
+
className: "sr-only"
|
|
97
|
+
}, image ? t("Remove image") : t("Upload image")))), /* @__PURE__ */React.createElement("p", {
|
|
98
|
+
className: "text-center font-medium"
|
|
99
|
+
}, t("Upload Image")), /* @__PURE__ */React.createElement("p", {
|
|
100
|
+
className: "text-center text-sm text-muted-foreground"
|
|
101
|
+
}, t("Max file size: 1MB")), /* @__PURE__ */React.createElement("input", {
|
|
102
|
+
type: "file",
|
|
103
|
+
ref: fileInputRef,
|
|
104
|
+
onChange: handleFileChange,
|
|
105
|
+
accept: "image/*",
|
|
106
|
+
className: "hidden"
|
|
107
|
+
})), /* @__PURE__ */React.createElement("div", {
|
|
108
|
+
className: "col-span-9 space-y-4"
|
|
109
|
+
}, /* @__PURE__ */React.createElement("div", {
|
|
110
|
+
className: "space-y-2"
|
|
111
|
+
}, /* @__PURE__ */React.createElement(_label.Label, {
|
|
112
|
+
htmlFor: "name"
|
|
113
|
+
}, t("Display Name")), /* @__PURE__ */React.createElement(_input.Input, {
|
|
114
|
+
id: "name",
|
|
115
|
+
value: name,
|
|
116
|
+
onChange: e => setName(e.target.value),
|
|
117
|
+
placeholder: t("Your name"),
|
|
118
|
+
disabled: isPending
|
|
119
|
+
})))), /* @__PURE__ */React.createElement(_button.Button, {
|
|
120
|
+
type: "submit",
|
|
121
|
+
disabled: isPending
|
|
122
|
+
}, isPending ? /* @__PURE__ */React.createElement(React.Fragment, null, /* @__PURE__ */React.createElement(_ui.Icon, {
|
|
123
|
+
icon: "solar:refresh-broken",
|
|
124
|
+
className: "h-4 w-4 mr-2 animate-spin"
|
|
125
|
+
}), t("Saving...")) : /* @__PURE__ */React.createElement(React.Fragment, null, /* @__PURE__ */React.createElement(_ui.Icon, {
|
|
126
|
+
icon: "solar:diskette-broken",
|
|
127
|
+
className: "h-4 w-4 mr-2"
|
|
128
|
+
}), t("Save Changes"))));
|
|
129
|
+
}
|
|
130
|
+
const initialUpdatePasswordState = {
|
|
131
|
+
message: "",
|
|
132
|
+
error: false
|
|
133
|
+
};
|
|
134
|
+
function UpdatePasswordForm() {
|
|
135
|
+
const [state, action, isPending] = (0, _react.useActionState)(_profile.updatePasswordAction, initialUpdatePasswordState);
|
|
136
|
+
const {
|
|
137
|
+
t
|
|
138
|
+
} = (0, _intl.useTranslation)();
|
|
139
|
+
const [currentPassword, setCurrentPassword] = (0, _react.useState)("");
|
|
140
|
+
const [newPassword, setNewPassword] = (0, _react.useState)("");
|
|
141
|
+
const [confirmPassword, setConfirmPassword] = (0, _react.useState)("");
|
|
142
|
+
return /* @__PURE__ */React.createElement("form", {
|
|
143
|
+
action,
|
|
144
|
+
className: "space-y-4"
|
|
145
|
+
}, /* @__PURE__ */React.createElement("div", {
|
|
146
|
+
className: "space-y-2"
|
|
147
|
+
}, /* @__PURE__ */React.createElement(_label.Label, {
|
|
148
|
+
htmlFor: "current-password"
|
|
149
|
+
}, t("Current Password")), /* @__PURE__ */React.createElement(_input.Input, {
|
|
150
|
+
type: "password",
|
|
151
|
+
id: "current-password",
|
|
152
|
+
name: "password",
|
|
153
|
+
value: currentPassword,
|
|
154
|
+
onChange: e => setCurrentPassword(e.target.value),
|
|
155
|
+
autoComplete: "current-password",
|
|
156
|
+
placeholder: t("Enter current password"),
|
|
157
|
+
required: true,
|
|
158
|
+
disabled: isPending
|
|
159
|
+
})), /* @__PURE__ */React.createElement("div", {
|
|
160
|
+
className: "space-y-2"
|
|
161
|
+
}, /* @__PURE__ */React.createElement(_label.Label, {
|
|
162
|
+
htmlFor: "new-password"
|
|
163
|
+
}, t("New Password")), /* @__PURE__ */React.createElement(_input.Input, {
|
|
164
|
+
type: "password",
|
|
165
|
+
id: "new-password",
|
|
166
|
+
name: "new_password",
|
|
167
|
+
value: newPassword,
|
|
168
|
+
onChange: e => setNewPassword(e.target.value),
|
|
169
|
+
autoComplete: "new-password",
|
|
170
|
+
placeholder: t("Enter new password"),
|
|
171
|
+
required: true,
|
|
172
|
+
disabled: isPending
|
|
173
|
+
})), /* @__PURE__ */React.createElement("div", {
|
|
174
|
+
className: "space-y-2"
|
|
175
|
+
}, /* @__PURE__ */React.createElement(_label.Label, {
|
|
176
|
+
htmlFor: "confirm-password"
|
|
177
|
+
}, t("Confirm New Password")), /* @__PURE__ */React.createElement(_input.Input, {
|
|
178
|
+
type: "password",
|
|
179
|
+
id: "confirm-password",
|
|
180
|
+
value: confirmPassword,
|
|
181
|
+
onChange: e => setConfirmPassword(e.target.value),
|
|
182
|
+
autoComplete: "new-password",
|
|
183
|
+
placeholder: t("Confirm new password"),
|
|
184
|
+
required: true,
|
|
185
|
+
disabled: isPending
|
|
186
|
+
})), state.message && /* @__PURE__ */React.createElement("p", {
|
|
187
|
+
className: (0, _ui.cn)("text-sm", state.error ? "text-destructive" : "text-green-600")
|
|
188
|
+
}, state.message), /* @__PURE__ */React.createElement(_button.Button, {
|
|
189
|
+
type: "submit",
|
|
190
|
+
disabled: isPending
|
|
191
|
+
}, isPending ? /* @__PURE__ */React.createElement(React.Fragment, null, /* @__PURE__ */React.createElement(_ui.Icon, {
|
|
192
|
+
icon: "solar:refresh-broken",
|
|
193
|
+
className: "h-4 w-4 mr-2 animate-spin"
|
|
194
|
+
}), t("Updating...")) : /* @__PURE__ */React.createElement(React.Fragment, null, /* @__PURE__ */React.createElement(_ui.Icon, {
|
|
195
|
+
icon: "solar:key-broken",
|
|
196
|
+
className: "h-4 w-4 mr-2"
|
|
197
|
+
}), t("Update Password"))));
|
|
198
|
+
}
|
|
199
|
+
const initialUpdateEmailState = {
|
|
200
|
+
message: "",
|
|
201
|
+
error: false
|
|
202
|
+
};
|
|
203
|
+
function UpdateEmailForm({
|
|
204
|
+
user
|
|
205
|
+
}) {
|
|
206
|
+
const [state, action, isPending] = (0, _react.useActionState)(_profile.updateEmailAction, initialUpdateEmailState);
|
|
207
|
+
const {
|
|
208
|
+
t
|
|
209
|
+
} = (0, _intl.useTranslation)();
|
|
210
|
+
const [email, setEmail] = (0, _react.useState)(user.email);
|
|
211
|
+
return /* @__PURE__ */React.createElement("form", {
|
|
212
|
+
action,
|
|
213
|
+
className: "space-y-4"
|
|
214
|
+
}, /* @__PURE__ */React.createElement("div", {
|
|
215
|
+
className: "space-y-2"
|
|
216
|
+
}, /* @__PURE__ */React.createElement(_label.Label, {
|
|
217
|
+
htmlFor: "new-email"
|
|
218
|
+
}, t("New Email Address")), /* @__PURE__ */React.createElement(_input.Input, {
|
|
219
|
+
type: "email",
|
|
220
|
+
id: "new-email",
|
|
221
|
+
name: "email",
|
|
222
|
+
value: email,
|
|
223
|
+
onChange: e => setEmail(e.target.value),
|
|
224
|
+
placeholder: t("Enter new email address"),
|
|
225
|
+
required: true,
|
|
226
|
+
disabled: isPending
|
|
227
|
+
})), state.message && /* @__PURE__ */React.createElement("p", {
|
|
228
|
+
className: (0, _ui.cn)("text-sm", state.error ? "text-destructive" : "text-green-600")
|
|
229
|
+
}, state.message), /* @__PURE__ */React.createElement(_button.Button, {
|
|
230
|
+
type: "submit",
|
|
231
|
+
disabled: isPending
|
|
232
|
+
}, isPending ? /* @__PURE__ */React.createElement(React.Fragment, null, /* @__PURE__ */React.createElement(_ui.Icon, {
|
|
233
|
+
icon: "solar:refresh-broken",
|
|
234
|
+
className: "h-4 w-4 mr-2 animate-spin"
|
|
235
|
+
}), t("Updating...")) : /* @__PURE__ */React.createElement(React.Fragment, null, /* @__PURE__ */React.createElement(_ui.Icon, {
|
|
236
|
+
icon: "solar:letter-broken",
|
|
237
|
+
className: "h-4 w-4 mr-2"
|
|
238
|
+
}), t("Update Email"))));
|
|
239
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FullUser } from "@arch-cadre/core";
|
|
2
|
+
export declare function UpdateProfileForm({ user }: {
|
|
3
|
+
user: FullUser;
|
|
4
|
+
}): import("react").JSX.Element;
|
|
5
|
+
export declare function UpdatePasswordForm(): import("react").JSX.Element;
|
|
6
|
+
export declare function UpdateEmailForm({ user }: {
|
|
7
|
+
user: FullUser;
|
|
8
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useTranslation } from "@arch-cadre/intl";
|
|
3
|
+
import { cn, Icon, toast } from "@arch-cadre/ui";
|
|
4
|
+
import {
|
|
5
|
+
Avatar,
|
|
6
|
+
AvatarFallback,
|
|
7
|
+
AvatarImage
|
|
8
|
+
} from "@arch-cadre/ui/components/avatar";
|
|
9
|
+
import { Button } from "@arch-cadre/ui/components/button";
|
|
10
|
+
import { Input } from "@arch-cadre/ui/components/input";
|
|
11
|
+
import { Label } from "@arch-cadre/ui/components/label";
|
|
12
|
+
import { useActionState, useRef, useState, useTransition } from "react";
|
|
13
|
+
import {
|
|
14
|
+
updateEmailAction,
|
|
15
|
+
updatePasswordAction,
|
|
16
|
+
updateProfileAction
|
|
17
|
+
} from "../../../actions/profile.mjs";
|
|
18
|
+
export function UpdateProfileForm({ user }) {
|
|
19
|
+
const { t } = useTranslation();
|
|
20
|
+
const [name, setName] = useState(user.name);
|
|
21
|
+
const [isPending, startTransition] = useTransition();
|
|
22
|
+
const [image, setImage] = useState(user.image);
|
|
23
|
+
const [avatar, setAvatar] = useState(void 0);
|
|
24
|
+
const fileInputRef = useRef(null);
|
|
25
|
+
const handleFileChange = (e) => {
|
|
26
|
+
const file = e.target.files?.[0];
|
|
27
|
+
if (file) {
|
|
28
|
+
setAvatar(file);
|
|
29
|
+
const reader = new FileReader();
|
|
30
|
+
reader.onload = (event) => {
|
|
31
|
+
setImage(event.target?.result);
|
|
32
|
+
};
|
|
33
|
+
reader.readAsDataURL(file);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const triggerFileInput = () => {
|
|
37
|
+
fileInputRef.current?.click();
|
|
38
|
+
};
|
|
39
|
+
const handleSubmit = async (e) => {
|
|
40
|
+
e.preventDefault();
|
|
41
|
+
if (!name.trim()) {
|
|
42
|
+
toast.error(t("Name is required"));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
startTransition(async () => {
|
|
46
|
+
const result = await updateProfileAction(name.trim(), avatar);
|
|
47
|
+
if (result.error) {
|
|
48
|
+
toast.error(result.error);
|
|
49
|
+
} else {
|
|
50
|
+
toast.success(result.message);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
return /* @__PURE__ */ React.createElement("form", { onSubmit: handleSubmit, className: "space-y-4" }, /* @__PURE__ */ React.createElement("div", { className: "grid grid-cols-12" }, /* @__PURE__ */ React.createElement("div", { className: "col-span-3 flex items-center flex-col" }, /* @__PURE__ */ React.createElement("div", { className: "relative mb-2" }, /* @__PURE__ */ React.createElement(Avatar, { className: "h-24 w-24 border-2 border-muted" }, /* @__PURE__ */ React.createElement(AvatarImage, { src: image || void 0, alt: name }), /* @__PURE__ */ React.createElement(AvatarFallback, { className: "text-3xl" }, name.charAt(0))), /* @__PURE__ */ React.createElement(
|
|
55
|
+
Button,
|
|
56
|
+
{
|
|
57
|
+
type: "button",
|
|
58
|
+
variant: "ghost",
|
|
59
|
+
size: "icon",
|
|
60
|
+
className: "absolute -top-0.5 -right-0.5 bg-accent rounded-full border-[3px] border-background h-8 w-8 hover:bg-accent",
|
|
61
|
+
onClick: () => {
|
|
62
|
+
if (image) {
|
|
63
|
+
setImage(null);
|
|
64
|
+
if (fileInputRef.current) {
|
|
65
|
+
fileInputRef.current.value = "";
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
triggerFileInput();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
image ? /* @__PURE__ */ React.createElement(
|
|
73
|
+
Icon,
|
|
74
|
+
{
|
|
75
|
+
icon: "solar:trash-bin-trash-broken",
|
|
76
|
+
className: "h-4 w-4 text-muted-foreground"
|
|
77
|
+
}
|
|
78
|
+
) : /* @__PURE__ */ React.createElement(
|
|
79
|
+
Icon,
|
|
80
|
+
{
|
|
81
|
+
icon: "solar:add-circle-broken",
|
|
82
|
+
className: "h-3 w-3 text-muted-foreground"
|
|
83
|
+
}
|
|
84
|
+
),
|
|
85
|
+
/* @__PURE__ */ React.createElement("span", { className: "sr-only" }, image ? t("Remove image") : t("Upload image"))
|
|
86
|
+
)), /* @__PURE__ */ React.createElement("p", { className: "text-center font-medium" }, t("Upload Image")), /* @__PURE__ */ React.createElement("p", { className: "text-center text-sm text-muted-foreground" }, t("Max file size: 1MB")), /* @__PURE__ */ React.createElement(
|
|
87
|
+
"input",
|
|
88
|
+
{
|
|
89
|
+
type: "file",
|
|
90
|
+
ref: fileInputRef,
|
|
91
|
+
onChange: handleFileChange,
|
|
92
|
+
accept: "image/*",
|
|
93
|
+
className: "hidden"
|
|
94
|
+
}
|
|
95
|
+
)), /* @__PURE__ */ React.createElement("div", { className: "col-span-9 space-y-4" }, /* @__PURE__ */ React.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React.createElement(Label, { htmlFor: "name" }, t("Display Name")), /* @__PURE__ */ React.createElement(
|
|
96
|
+
Input,
|
|
97
|
+
{
|
|
98
|
+
id: "name",
|
|
99
|
+
value: name,
|
|
100
|
+
onChange: (e) => setName(e.target.value),
|
|
101
|
+
placeholder: t("Your name"),
|
|
102
|
+
disabled: isPending
|
|
103
|
+
}
|
|
104
|
+
)))), /* @__PURE__ */ React.createElement(Button, { type: "submit", disabled: isPending }, isPending ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
|
|
105
|
+
Icon,
|
|
106
|
+
{
|
|
107
|
+
icon: "solar:refresh-broken",
|
|
108
|
+
className: "h-4 w-4 mr-2 animate-spin"
|
|
109
|
+
}
|
|
110
|
+
), t("Saving...")) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Icon, { icon: "solar:diskette-broken", className: "h-4 w-4 mr-2" }), t("Save Changes"))));
|
|
111
|
+
}
|
|
112
|
+
const initialUpdatePasswordState = {
|
|
113
|
+
message: "",
|
|
114
|
+
error: false
|
|
115
|
+
};
|
|
116
|
+
export function UpdatePasswordForm() {
|
|
117
|
+
const [state, action, isPending] = useActionState(
|
|
118
|
+
updatePasswordAction,
|
|
119
|
+
initialUpdatePasswordState
|
|
120
|
+
);
|
|
121
|
+
const { t } = useTranslation();
|
|
122
|
+
const [currentPassword, setCurrentPassword] = useState("");
|
|
123
|
+
const [newPassword, setNewPassword] = useState("");
|
|
124
|
+
const [confirmPassword, setConfirmPassword] = useState("");
|
|
125
|
+
return /* @__PURE__ */ React.createElement("form", { action, className: "space-y-4" }, /* @__PURE__ */ React.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React.createElement(Label, { htmlFor: "current-password" }, t("Current Password")), /* @__PURE__ */ React.createElement(
|
|
126
|
+
Input,
|
|
127
|
+
{
|
|
128
|
+
type: "password",
|
|
129
|
+
id: "current-password",
|
|
130
|
+
name: "password",
|
|
131
|
+
value: currentPassword,
|
|
132
|
+
onChange: (e) => setCurrentPassword(e.target.value),
|
|
133
|
+
autoComplete: "current-password",
|
|
134
|
+
placeholder: t("Enter current password"),
|
|
135
|
+
required: true,
|
|
136
|
+
disabled: isPending
|
|
137
|
+
}
|
|
138
|
+
)), /* @__PURE__ */ React.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React.createElement(Label, { htmlFor: "new-password" }, t("New Password")), /* @__PURE__ */ React.createElement(
|
|
139
|
+
Input,
|
|
140
|
+
{
|
|
141
|
+
type: "password",
|
|
142
|
+
id: "new-password",
|
|
143
|
+
name: "new_password",
|
|
144
|
+
value: newPassword,
|
|
145
|
+
onChange: (e) => setNewPassword(e.target.value),
|
|
146
|
+
autoComplete: "new-password",
|
|
147
|
+
placeholder: t("Enter new password"),
|
|
148
|
+
required: true,
|
|
149
|
+
disabled: isPending
|
|
150
|
+
}
|
|
151
|
+
)), /* @__PURE__ */ React.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React.createElement(Label, { htmlFor: "confirm-password" }, t("Confirm New Password")), /* @__PURE__ */ React.createElement(
|
|
152
|
+
Input,
|
|
153
|
+
{
|
|
154
|
+
type: "password",
|
|
155
|
+
id: "confirm-password",
|
|
156
|
+
value: confirmPassword,
|
|
157
|
+
onChange: (e) => setConfirmPassword(e.target.value),
|
|
158
|
+
autoComplete: "new-password",
|
|
159
|
+
placeholder: t("Confirm new password"),
|
|
160
|
+
required: true,
|
|
161
|
+
disabled: isPending
|
|
162
|
+
}
|
|
163
|
+
)), state.message && /* @__PURE__ */ React.createElement(
|
|
164
|
+
"p",
|
|
165
|
+
{
|
|
166
|
+
className: cn(
|
|
167
|
+
"text-sm",
|
|
168
|
+
state.error ? "text-destructive" : "text-green-600"
|
|
169
|
+
)
|
|
170
|
+
},
|
|
171
|
+
state.message
|
|
172
|
+
), /* @__PURE__ */ React.createElement(Button, { type: "submit", disabled: isPending }, isPending ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
|
|
173
|
+
Icon,
|
|
174
|
+
{
|
|
175
|
+
icon: "solar:refresh-broken",
|
|
176
|
+
className: "h-4 w-4 mr-2 animate-spin"
|
|
177
|
+
}
|
|
178
|
+
), t("Updating...")) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Icon, { icon: "solar:key-broken", className: "h-4 w-4 mr-2" }), t("Update Password"))));
|
|
179
|
+
}
|
|
180
|
+
const initialUpdateEmailState = {
|
|
181
|
+
message: "",
|
|
182
|
+
error: false
|
|
183
|
+
};
|
|
184
|
+
export function UpdateEmailForm({ user }) {
|
|
185
|
+
const [state, action, isPending] = useActionState(
|
|
186
|
+
updateEmailAction,
|
|
187
|
+
initialUpdateEmailState
|
|
188
|
+
);
|
|
189
|
+
const { t } = useTranslation();
|
|
190
|
+
const [email, setEmail] = useState(user.email);
|
|
191
|
+
return /* @__PURE__ */ React.createElement("form", { action, className: "space-y-4" }, /* @__PURE__ */ React.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React.createElement(Label, { htmlFor: "new-email" }, t("New Email Address")), /* @__PURE__ */ React.createElement(
|
|
192
|
+
Input,
|
|
193
|
+
{
|
|
194
|
+
type: "email",
|
|
195
|
+
id: "new-email",
|
|
196
|
+
name: "email",
|
|
197
|
+
value: email,
|
|
198
|
+
onChange: (e) => setEmail(e.target.value),
|
|
199
|
+
placeholder: t("Enter new email address"),
|
|
200
|
+
required: true,
|
|
201
|
+
disabled: isPending
|
|
202
|
+
}
|
|
203
|
+
)), state.message && /* @__PURE__ */ React.createElement(
|
|
204
|
+
"p",
|
|
205
|
+
{
|
|
206
|
+
className: cn(
|
|
207
|
+
"text-sm",
|
|
208
|
+
state.error ? "text-destructive" : "text-green-600"
|
|
209
|
+
)
|
|
210
|
+
},
|
|
211
|
+
state.message
|
|
212
|
+
), /* @__PURE__ */ React.createElement(Button, { type: "submit", disabled: isPending }, isPending ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
|
|
213
|
+
Icon,
|
|
214
|
+
{
|
|
215
|
+
icon: "solar:refresh-broken",
|
|
216
|
+
className: "h-4 w-4 mr-2 animate-spin"
|
|
217
|
+
}
|
|
218
|
+
), t("Updating...")) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Icon, { icon: "solar:letter-broken", className: "h-4 w-4 mr-2" }), t("Update Email"))));
|
|
219
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.UserDropdownLink = UserDropdownLink;
|
|
8
|
+
var _intl = require("@arch-cadre/intl");
|
|
9
|
+
var _ui = require("@arch-cadre/ui");
|
|
10
|
+
var _dropdownMenu = require("@arch-cadre/ui/components/dropdown-menu");
|
|
11
|
+
var _link = _interopRequireDefault(require("next/link"));
|
|
12
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
13
|
+
function UserDropdownLink() {
|
|
14
|
+
const root = "/kryo";
|
|
15
|
+
const {
|
|
16
|
+
t
|
|
17
|
+
} = (0, _intl.useTranslation)();
|
|
18
|
+
return /* @__PURE__ */React.createElement(React.Fragment, null, /* @__PURE__ */React.createElement(_dropdownMenu.DropdownMenuGroup, null, /* @__PURE__ */React.createElement(_link.default, {
|
|
19
|
+
href: `${root}/profile`
|
|
20
|
+
}, /* @__PURE__ */React.createElement(_dropdownMenu.DropdownMenuItem, {
|
|
21
|
+
className: "cursor-pointer "
|
|
22
|
+
}, /* @__PURE__ */React.createElement(_ui.Icon, {
|
|
23
|
+
icon: "solar:user-circle-broken"
|
|
24
|
+
}), t("Profile Settings")))));
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function UserDropdownLink(): import("react").JSX.Element;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useTranslation } from "@arch-cadre/intl";
|
|
3
|
+
import { Icon } from "@arch-cadre/ui";
|
|
4
|
+
import {
|
|
5
|
+
DropdownMenuGroup,
|
|
6
|
+
DropdownMenuItem
|
|
7
|
+
} from "@arch-cadre/ui/components/dropdown-menu";
|
|
8
|
+
import Link from "next/link";
|
|
9
|
+
export function UserDropdownLink() {
|
|
10
|
+
const root = "/kryo";
|
|
11
|
+
const { t } = useTranslation();
|
|
12
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(DropdownMenuGroup, null, /* @__PURE__ */ React.createElement(Link, { href: `${root}/profile` }, /* @__PURE__ */ React.createElement(DropdownMenuItem, { className: "cursor-pointer " }, /* @__PURE__ */ React.createElement(Icon, { icon: "solar:user-circle-broken" }), t("Profile Settings")))));
|
|
13
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
module.exports = ProfileSettingsPage;
|
|
7
|
+
var _server = require("@arch-cadre/core/server");
|
|
8
|
+
var _server2 = require("@arch-cadre/intl/server");
|
|
9
|
+
var _modules = require("@arch-cadre/modules");
|
|
10
|
+
var _navigation = require("next/navigation");
|
|
11
|
+
var _components = require("./components.cjs");
|
|
12
|
+
async function ProfileSettingsPage() {
|
|
13
|
+
const {
|
|
14
|
+
session,
|
|
15
|
+
user
|
|
16
|
+
} = await (0, _server.getCurrentSession)();
|
|
17
|
+
const {
|
|
18
|
+
t
|
|
19
|
+
} = await (0, _server2.getTranslation)();
|
|
20
|
+
if (session === null || user === null) {
|
|
21
|
+
return (0, _navigation.redirect)("/signin");
|
|
22
|
+
}
|
|
23
|
+
const security = await (0, _server.checkSecurity)(session, user);
|
|
24
|
+
if (!security.satisfied && security.redirect) {
|
|
25
|
+
return (0, _navigation.redirect)(security.redirect);
|
|
26
|
+
}
|
|
27
|
+
return /* @__PURE__ */React.createElement("div", {
|
|
28
|
+
className: "space-y-6 max-w-2xl"
|
|
29
|
+
}, /* @__PURE__ */React.createElement("div", null, /* @__PURE__ */React.createElement("h2", {
|
|
30
|
+
className: "text-2xl font-bold"
|
|
31
|
+
}, t("Profile Settings")), /* @__PURE__ */React.createElement("p", {
|
|
32
|
+
className: "text-muted-foreground"
|
|
33
|
+
}, t("Manage your account settings and profile information."))), /* @__PURE__ */React.createElement(_components.UpdateProfileForm, {
|
|
34
|
+
user
|
|
35
|
+
}), /* @__PURE__ */React.createElement(_components.UpdateEmailForm, {
|
|
36
|
+
user
|
|
37
|
+
}), /* @__PURE__ */React.createElement(_components.UpdatePasswordForm, null), /* @__PURE__ */React.createElement(_modules.ExtensionPoint, {
|
|
38
|
+
module: "user-profile",
|
|
39
|
+
point: "settings:extra-sections"
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function ProfileSettingsPage(): Promise<import("react").JSX.Element>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { checkSecurity, getCurrentSession } from "@arch-cadre/core/server";
|
|
2
|
+
import { getTranslation } from "@arch-cadre/intl/server";
|
|
3
|
+
import { ExtensionPoint } from "@arch-cadre/modules";
|
|
4
|
+
import { redirect } from "next/navigation";
|
|
5
|
+
import {
|
|
6
|
+
UpdateEmailForm,
|
|
7
|
+
UpdatePasswordForm,
|
|
8
|
+
UpdateProfileForm
|
|
9
|
+
} from "./components.mjs";
|
|
10
|
+
export default async function ProfileSettingsPage() {
|
|
11
|
+
const { session, user } = await getCurrentSession();
|
|
12
|
+
const { t } = await getTranslation();
|
|
13
|
+
if (session === null || user === null) {
|
|
14
|
+
return redirect("/signin");
|
|
15
|
+
}
|
|
16
|
+
const security = await checkSecurity(session, user);
|
|
17
|
+
if (!security.satisfied && security.redirect) {
|
|
18
|
+
return redirect(security.redirect);
|
|
19
|
+
}
|
|
20
|
+
return /* @__PURE__ */ React.createElement("div", { className: "space-y-6 max-w-2xl" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h2", { className: "text-2xl font-bold" }, t("Profile Settings")), /* @__PURE__ */ React.createElement("p", { className: "text-muted-foreground" }, t("Manage your account settings and profile information."))), /* @__PURE__ */ React.createElement(UpdateProfileForm, { user }), /* @__PURE__ */ React.createElement(UpdateEmailForm, { user }), /* @__PURE__ */ React.createElement(UpdatePasswordForm, null), /* @__PURE__ */ React.createElement(ExtensionPoint, { module: "user-profile", point: "settings:extra-sections" }));
|
|
21
|
+
}
|