@myrjfa/ui 2.0.1 → 2.1.2
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/LICENSE +201 -201
- package/README.md +2 -2
- package/dist/chat/ChatMain.d.ts +3 -1
- package/dist/chat/ChatMain.d.ts.map +1 -1
- package/dist/chat/ChatMain.js +2 -1
- package/dist/chat/MessageBubble.js +4 -4
- package/dist/chat/MessageInput.d.ts +1 -1
- package/dist/chat/MessageInput.d.ts.map +1 -1
- package/dist/chat/components/ParticipantSelector.d.ts +1 -1
- package/dist/chat/components/ParticipantSelector.d.ts.map +1 -1
- package/dist/dashboard/app-sidebar.js +3 -3
- package/dist/dashboard/data-table.d.ts +2 -1
- package/dist/dashboard/data-table.d.ts.map +1 -1
- package/dist/dashboard/data-table.js +3 -3
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/main/navbar.js +5 -5
- package/dist/main/review-page.d.ts +1 -1
- package/dist/main/review-page.d.ts.map +1 -1
- package/dist/main/review-page.js +49 -9
- package/dist/misc/alertdialogclaude.d.ts.map +1 -1
- package/dist/misc/alertdialogclaude.js +1 -1
- package/dist/misc/command.js +1 -1
- package/dist/misc/dashboard/action-buttons.js +31 -0
- package/dist/misc/document-upload-button.d.ts +12 -0
- package/dist/misc/document-upload-button.d.ts.map +1 -0
- package/dist/misc/document-upload-button.js +27 -0
- package/dist/misc/empty-state.d.ts +10 -0
- package/dist/misc/empty-state.d.ts.map +1 -0
- package/dist/misc/empty-state.js +5 -0
- package/dist/misc/filter.d.ts.map +1 -1
- package/dist/misc/filter.js +1 -1
- package/dist/misc/image-compressor.d.ts +29 -0
- package/dist/misc/image-compressor.d.ts.map +1 -0
- package/dist/misc/image-compressor.js +70 -0
- package/dist/misc/image-cropper.d.ts +10 -0
- package/dist/misc/image-cropper.d.ts.map +1 -0
- package/dist/misc/image-cropper.js +44 -0
- package/dist/misc/input.d.ts.map +1 -1
- package/dist/misc/input.js +1 -1
- package/dist/misc/misc/alertdialogclaude.js +221 -0
- package/dist/misc/misc/button.js +46 -0
- package/dist/misc/misc/dropdown-menu.js +136 -0
- package/dist/misc/multiImageUploader.d.ts +2 -1
- package/dist/misc/multiImageUploader.d.ts.map +1 -1
- package/dist/misc/multiImageUploader.js +26 -2
- package/dist/misc/newsLetter.js +1 -1
- package/dist/misc/notification.js +2 -2
- package/dist/misc/popover.d.ts.map +1 -1
- package/dist/misc/popover.js +1 -1
- package/dist/misc/pwa-install-button.d.ts +9 -0
- package/dist/misc/pwa-install-button.d.ts.map +1 -0
- package/dist/misc/pwa-install-button.js +158 -0
- package/dist/misc/select.d.ts.map +1 -1
- package/dist/misc/select.js +3 -3
- package/dist/misc/skeleton.d.ts +36 -1
- package/dist/misc/skeleton.d.ts.map +1 -1
- package/dist/misc/skeleton.js +40 -3
- package/dist/misc/tile.js +3 -3
- package/dist/misc/whatsapp-help.d.ts +9 -0
- package/dist/misc/whatsapp-help.d.ts.map +1 -0
- package/dist/misc/whatsapp-help.js +13 -0
- package/dist/profile/socialLink.d.ts +1 -1
- package/dist/profile/socialLink.d.ts.map +1 -1
- package/dist/profile/socialLink.js +49 -19
- package/package.json +5 -4
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,0BAA0B,CAAC;AACzC,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,eAAe,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACpG,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,sBAAsB,CAAC;AACrC,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC;AACpC,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,4BAA4B,CAAC;AAC3C,cAAc,yBAAyB,CAAC;AACxC,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,cAAc,kCAAkC,CAAC;AACjD,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AAGrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,iBAAiB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// Export all your shared UI components
|
|
2
1
|
export * from './misc/button';
|
|
2
|
+
export * from './misc/empty-state';
|
|
3
3
|
export * from './misc/accordion';
|
|
4
4
|
export * from './misc/alert';
|
|
5
5
|
export * from './misc/alertdialogclaude';
|
|
@@ -57,3 +57,9 @@ export * from './main/footer';
|
|
|
57
57
|
export * from './main/navbar';
|
|
58
58
|
export * from './main/paginationBar';
|
|
59
59
|
export * from './main/review-page';
|
|
60
|
+
export * from './misc/image-cropper';
|
|
61
|
+
// New components — features #9, #10, #21, #26
|
|
62
|
+
export * from './misc/pwa-install-button';
|
|
63
|
+
export * from './misc/whatsapp-help';
|
|
64
|
+
export * from './misc/image-compressor';
|
|
65
|
+
export * from './misc/skeleton';
|
package/dist/main/navbar.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { Popover, PopoverContent, PopoverTrigger } from "@radix-ui/react-popover";
|
|
4
4
|
import { Separator } from "@radix-ui/react-separator";
|
|
5
|
-
import { logoutUser } from "@myrjfa/state";
|
|
6
|
-
import { isLoggedInAtom, userAtom, userLoadingAtom } from "@myrjfa/state";
|
|
7
|
-
import { opportunityTypeOptions } from "@myrjfa/state";
|
|
5
|
+
import { logoutUser } from "@myrjfa/state/dist/lib/actions/auth";
|
|
6
|
+
import { isLoggedInAtom, userAtom, userLoadingAtom } from "@myrjfa/state/dist/lib/userAtom";
|
|
7
|
+
import { opportunityTypeOptions } from "@myrjfa/state/dist/lib/models/opportunities/opportunity";
|
|
8
8
|
import { useAtom, useAtomValue } from "jotai";
|
|
9
|
-
import { ChevronDown, Globe, Heart, LayoutDashboardIcon, Store, Target, Luggage,
|
|
9
|
+
import { ChevronDown, Globe, Heart, LayoutDashboardIcon, Store, Target, Luggage, Compass, Mountain, Users, Sparkles, BedDouble, Ticket, Car } from "lucide-react";
|
|
10
10
|
import Link from "next/link";
|
|
11
11
|
import { useRouter, usePathname } from "next/navigation";
|
|
12
12
|
import { useEffect, useState } from "react";
|
|
@@ -66,7 +66,7 @@ export function Navbar({ dark = false }) {
|
|
|
66
66
|
const navItemColor = effectiveDark
|
|
67
67
|
? "text-white/90 hover:text-white"
|
|
68
68
|
: "text-muted-foreground hover:text-foreground";
|
|
69
|
-
return (_jsx("header", { className: `fixed top-0 left-0 z-[100] mx-auto transition-all duration-300 w-full ${navbarBg} ${textColor} ${navbarBorder}`, children: _jsxs("div", { className: "mx-auto flex items-center justify-between gap-10 py-4 px-4 lg:px-8", children: [_jsxs(Link, { href: "/", className: "flex items-center gap-3", children: [_jsxs(Avatar, { className: "ml-2 -mr-4.5", children: [_jsx(AvatarImage, { src: "/images/logoicon.webp" }), _jsxs(AvatarFallback, { children: [(currentUser === null || currentUser === void 0 ? void 0 : currentUser.firstName.charAt(0)) || "D", (currentUser === null || currentUser === void 0 ? void 0 : currentUser.lastName.charAt(0)) || "P"] })] }), _jsx("span", { className: "font-heading text-xl font-bold", children: "rippeaze" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("nav", { className: "hidden md:flex items-center gap-10 justify-end", children: [_jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { className: `flex items-center gap-1 text-sm font-medium cursor-pointer ${navItemColor}`, children: [_jsx(Target, { className: "h-5 w-5" }), _jsx("span", { children: "Opportunities" }), _jsx(ChevronDown, { className: "h-4 w-4" })] }) }), _jsx(PopoverContent, { className: "w-56 p-2 mt-3 bg-white rounded-lg shadow-lg border", align: "start", sideOffset: 5, children: _jsx("div", { className: "flex flex-col", children: opportunityTypeOptions.map((opt) => (_jsxs(Link, { href: `/opportunities?type=${opt.value}`, className: "px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded-md flex flex-col", children: [_jsx("span", { className: "font-medium", children: opt.label }), _jsx("span", { className: "text-xs text-gray-500", children: opt.description })] }, opt.value))) }) })] }), _jsx(NavItem, { icon: _jsx(Luggage, { className: "h-5 w-5" }), label: "Packages", href: "/packages", className: navItemColor }), _jsx(NavItem, { icon: _jsx(Globe, { className: "h-5 w-5" }), label: "Blogs", href: "/blogs", className: navItemColor }), _jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { className: `flex items-center gap-1 text-sm font-medium cursor-pointer ${navItemColor}`, children: [_jsx(Compass, { className: "h-5 w-5" }), _jsx("span", { children: "Explore" }), _jsx(ChevronDown, { className: "h-4 w-4" })] }) }), _jsx(PopoverContent, { className: "w-64 p-2 mt-3 bg-white rounded-xl shadow-xl border", align: "start", sideOffset: 5, children: _jsxs("div", { className: "flex flex-col gap-0.5", children: [_jsx("p", { className: "text-[10px] font-black uppercase tracking-widest text-gray-400 px-3 pt-1 pb-0.5", children: "Travel" }), _jsxs(Link, { href: "/stays", className: "px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded-lg flex items-center gap-3", children: [_jsx(BedDouble, { className: "h-4 w-4 text-primary flex-shrink-0" }), _jsxs("div", { children: [_jsx("p", { className: "font-semibold", children: "Stays" }), _jsx("p", { className: "text-xs text-gray-400", children: "Hotels, hostels & homestays" })] })] }), _jsxs(Link, { href: "/events", className: "px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded-lg flex items-center gap-3", children: [_jsx(Ticket, { className: "h-4 w-4 text-primary flex-shrink-0" }), _jsxs("div", { children: [_jsx("p", { className: "font-semibold", children: "Events" }), _jsx("p", { className: "text-xs text-gray-400", children: "Concerts, workshops & more" })] })] }), _jsxs(Link, { href: "/adventures", className: "px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded-lg flex items-center gap-3", children: [_jsx(Mountain, { className: "h-4 w-4 text-primary flex-shrink-0" }), _jsxs("div", { children: [_jsx("p", { className: "font-semibold", children: "Adventures" }), _jsx("p", { className: "text-xs text-gray-400", children: "Tours & thrilling activities" })] })] }), _jsxs(Link, { href: "/rentals", className: "px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded-lg flex items-center gap-3", children: [_jsx(Car, { className: "h-4 w-4 text-primary flex-shrink-0" }), _jsxs("div", { children: [_jsx("p", { className: "font-semibold", children: "Rentals" }), _jsx("p", { className: "text-xs text-gray-400", children: "Bikes, cars & vehicles" })] })] }), _jsx("p", { className: "text-[10px] font-black uppercase tracking-widest text-gray-400 px-3 pt-2 pb-0.5", children: "For You" }), _jsxs(Link, { href: "/find-companion", className: "px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded-lg flex items-center gap-3", children: [_jsx(Users, { className: "h-4 w-4 text-primary flex-shrink-0" }), _jsxs("div", { children: [_jsx("p", { className: "font-semibold", children: "Find Companions" }), _jsx("p", { className: "text-xs text-gray-400", children: "Connect with travel buddies" })] })] }), _jsxs(Link, { href: "/plan", className: "px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded-lg flex items-center gap-3", children: [_jsx(Sparkles, { className: "h-4 w-4 text-primary flex-shrink-0" }), _jsxs("div", { children: [_jsx("p", { className: "font-semibold", children: "Plan a Trip" }), _jsx("p", { className: "text-xs text-gray-400", children: "AI-powered itinerary builder" })] })] })] }) })] }),
|
|
69
|
+
return (_jsx("header", { className: `fixed top-0 left-0 z-[100] mx-auto transition-all duration-300 w-full ${navbarBg} ${textColor} ${navbarBorder}`, children: _jsxs("div", { className: "mx-auto flex items-center justify-between gap-10 py-4 px-4 lg:px-8", children: [_jsxs(Link, { href: "/", className: "flex items-center gap-3", children: [_jsxs(Avatar, { className: "ml-2 -mr-4.5", children: [_jsx(AvatarImage, { src: "/images/logoicon.webp" }), _jsxs(AvatarFallback, { children: [(currentUser === null || currentUser === void 0 ? void 0 : currentUser.firstName.charAt(0)) || "D", (currentUser === null || currentUser === void 0 ? void 0 : currentUser.lastName.charAt(0)) || "P"] })] }), _jsx("span", { className: "font-heading text-xl font-bold", children: "rippeaze" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("nav", { className: "hidden md:flex items-center gap-10 justify-end", children: [_jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { className: `flex items-center gap-1 text-sm font-medium cursor-pointer ${navItemColor}`, children: [_jsx(Target, { className: "h-5 w-5" }), _jsx("span", { children: "Opportunities" }), _jsx(ChevronDown, { className: "h-4 w-4" })] }) }), _jsx(PopoverContent, { className: "w-56 p-2 mt-3 bg-white rounded-lg shadow-lg border", align: "start", sideOffset: 5, children: _jsx("div", { className: "flex flex-col", children: opportunityTypeOptions.map((opt) => (_jsxs(Link, { href: `/opportunities?type=${opt.value}`, className: "px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded-md flex flex-col", children: [_jsx("span", { className: "font-medium", children: opt.label }), _jsx("span", { className: "text-xs text-gray-500", children: opt.description })] }, opt.value))) }) })] }), _jsx(NavItem, { icon: _jsx(Luggage, { className: "h-5 w-5" }), label: "Packages", href: "/packages", className: navItemColor }), _jsx(NavItem, { icon: _jsx(Globe, { className: "h-5 w-5" }), label: "Blogs", href: "/blogs", className: navItemColor }), _jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { className: `flex items-center gap-1 text-sm font-medium cursor-pointer ${navItemColor}`, children: [_jsx(Compass, { className: "h-5 w-5" }), _jsx("span", { children: "Explore" }), _jsx(ChevronDown, { className: "h-4 w-4" })] }) }), _jsx(PopoverContent, { className: "w-64 p-2 mt-3 bg-white rounded-xl shadow-xl border", align: "start", sideOffset: 5, children: _jsxs("div", { className: "flex flex-col gap-0.5", children: [_jsx("p", { className: "text-[10px] font-black uppercase tracking-widest text-gray-400 px-3 pt-1 pb-0.5", children: "Travel" }), _jsxs(Link, { href: "/stays", className: "px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded-lg flex items-center gap-3", children: [_jsx(BedDouble, { className: "h-4 w-4 text-primary flex-shrink-0" }), _jsxs("div", { children: [_jsx("p", { className: "font-semibold", children: "Stays" }), _jsx("p", { className: "text-xs text-gray-400", children: "Hotels, hostels & homestays" })] })] }), _jsxs(Link, { href: "/events", className: "px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded-lg flex items-center gap-3", children: [_jsx(Ticket, { className: "h-4 w-4 text-primary flex-shrink-0" }), _jsxs("div", { children: [_jsx("p", { className: "font-semibold", children: "Events" }), _jsx("p", { className: "text-xs text-gray-400", children: "Concerts, workshops & more" })] })] }), _jsxs(Link, { href: "/adventures", className: "px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded-lg flex items-center gap-3", children: [_jsx(Mountain, { className: "h-4 w-4 text-primary flex-shrink-0" }), _jsxs("div", { children: [_jsx("p", { className: "font-semibold", children: "Adventures" }), _jsx("p", { className: "text-xs text-gray-400", children: "Tours & thrilling activities" })] })] }), _jsxs(Link, { href: "/rentals", className: "px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded-lg flex items-center gap-3", children: [_jsx(Car, { className: "h-4 w-4 text-primary flex-shrink-0" }), _jsxs("div", { children: [_jsx("p", { className: "font-semibold", children: "Rentals" }), _jsx("p", { className: "text-xs text-gray-400", children: "Bikes, cars & vehicles" })] })] }), _jsx("p", { className: "text-[10px] font-black uppercase tracking-widest text-gray-400 px-3 pt-2 pb-0.5", children: "For You" }), _jsxs(Link, { href: "/find-companion", className: "px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded-lg flex items-center gap-3", children: [_jsx(Users, { className: "h-4 w-4 text-primary flex-shrink-0" }), _jsxs("div", { children: [_jsx("p", { className: "font-semibold", children: "Find Companions" }), _jsx("p", { className: "text-xs text-gray-400", children: "Connect with travel buddies" })] })] }), _jsxs(Link, { href: "/plan", className: "px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded-lg flex items-center gap-3", children: [_jsx(Sparkles, { className: "h-4 w-4 text-primary flex-shrink-0" }), _jsxs("div", { children: [_jsx("p", { className: "font-semibold", children: "Plan a Trip" }), _jsx("p", { className: "text-xs text-gray-400", children: "AI-powered itinerary builder" })] })] })] }) })] }), currentUser ? _jsx(NavItem, { icon: _jsx(Store, { className: "h-5 w-5" }), label: "Become a Partner", href: "/contact", className: navItemColor })
|
|
70
70
|
: _jsx(NavItem, { icon: _jsx(Store, { className: "h-5 w-5" }), label: "Register as Business", href: "/auth/register?role=host", className: navItemColor }), isLoggedIn && _jsx(NotificationBell, { navItemColor: navItemColor })] }), _jsx("div", { className: "hidden md:flex items-center gap-2", children: _jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx(Button, { variant: "link", className: "flex w-full gap-1 cursor-pointer", children: _jsxs(Avatar, { className: "ml-5 mr-4.5", children: [_jsx(AvatarImage, { src: (_a = currentUser === null || currentUser === void 0 ? void 0 : currentUser.profilePic) !== null && _a !== void 0 ? _a : "/images/profile_placeholder.webp" }), _jsxs(AvatarFallback, { children: [(currentUser === null || currentUser === void 0 ? void 0 : currentUser.firstName.charAt(0)) || "D", (currentUser === null || currentUser === void 0 ? void 0 : currentUser.lastName.charAt(0)) || "P"] })] }) }) }), _jsx(PopoverContent, { className: `w-full p-0 mt-3 bg-${dark ? "primary" : "white"} rounded-lg shadow-lg`, align: "end", sideOffset: 5, children: _jsxs("div", { className: "py-2 w-full", children: [_jsxs("div", { className: "px-4 py-3 border-b w-full", children: [_jsx("p", { className: "text-sm font-medium", children: (currentUser === null || currentUser === void 0 ? void 0 : currentUser.username) || "Guest" }), _jsx("p", { className: "text-xs text-gray-500", children: (currentUser === null || currentUser === void 0 ? void 0 : currentUser.email) || "user@example.com" })] }), _jsxs("div", { className: "flex flex-col", children: [isLoggedIn ?
|
|
71
71
|
_jsxs(Link, { href: `/profile/${(currentUser === null || currentUser === void 0 ? void 0 : currentUser.role) || "user"}/${(currentUser === null || currentUser === void 0 ? void 0 : currentUser.username) || "Guest"}`, className: "w-full py-2 px-4 text-sm text-gray-700 hover:bg-gray-100 flex items-center gap-2", children: [_jsxs(Avatar, { className: "w-4 h-4", children: [_jsx(AvatarImage, { src: (_b = currentUser === null || currentUser === void 0 ? void 0 : currentUser.profilePic) !== null && _b !== void 0 ? _b : "/images/profile_placeholder.webp" }), _jsx(AvatarFallback, { children: "U" })] }), "Profile"] }) :
|
|
72
72
|
_jsxs(Button, { variant: "ghost", onClick: () => router.push('/auth/login?role=user'), className: "w-full py-2 px-4 text-sm text-gray-700 hover:bg-gray-100 flex items-center gap-2 justify-start", children: [_jsxs(Avatar, { className: "w-4 h-4", children: [_jsx(AvatarImage, { src: "/images/profile_placeholder.webp" }), _jsx(AvatarFallback, { children: "U" })] }), "Login As Volunteer"] }), isLoggedIn && _jsxs(Link, { href: (currentUser === null || currentUser === void 0 ? void 0 : currentUser.role) === 'host' || (currentUser === null || currentUser === void 0 ? void 0 : currentUser.role) === 'admin' ? `/dashboard/host/overview` : `/overview/${(currentUser === null || currentUser === void 0 ? void 0 : currentUser.role) || 'user'}`, className: "w-full py-2 px-4 text-sm text-gray-700 hover:bg-gray-100 flex items-center gap-2", children: [_jsx(LayoutDashboardIcon, { className: "h-4 w-4" }), "Dashboard"] }), isLoggedIn && ((currentUser === null || currentUser === void 0 ? void 0 : currentUser.role) === "user" || (currentUser === null || currentUser === void 0 ? void 0 : currentUser.role) == null) &&
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"review-page.d.ts","sourceRoot":"","sources":["../../src/main/review-page.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,MAAM,EAAqB,gBAAgB,EAAc,MAAM,
|
|
1
|
+
{"version":3,"file":"review-page.d.ts","sourceRoot":"","sources":["../../src/main/review-page.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,MAAM,EAAqB,gBAAgB,EAAc,MAAM,sCAAsC,CAAC;AAoD/G,UAAU,eAAe;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,cAAc,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;CACzC;AAED,wBAAgB,UAAU,CAAC,EACvB,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,UAA0B,EAC1B,QAAgB,EAChB,cAAmB,EACnB,cAAyB,GAC5B,EAAE,eAAe,2CAwQjB"}
|
package/dist/main/review-page.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
4
|
-
import { addReview, deleteReview, editReview, getReviewsById } from "@myrjfa/state";
|
|
5
|
-
import { reviewBasicSchema } from "@myrjfa/state";
|
|
6
|
-
import { userAtom, userLoadingAtom } from "@myrjfa/state";
|
|
7
|
-
import { formatDate } from "@myrjfa/state";
|
|
4
|
+
import { addReview, deleteReview, editReview, getReviewsById } from "@myrjfa/state/dist/lib/actions/actions";
|
|
5
|
+
import { reviewBasicSchema } from "@myrjfa/state/dist/lib/models/review";
|
|
6
|
+
import { userAtom, userLoadingAtom } from "@myrjfa/state/dist/lib/userAtom";
|
|
7
|
+
import { formatDate } from "@myrjfa/state/dist/lib/utils";
|
|
8
8
|
import { useAtomValue } from "jotai";
|
|
9
9
|
import { StarIcon } from "lucide-react";
|
|
10
10
|
import { useEffect, useState } from "react";
|
|
@@ -23,13 +23,17 @@ export function ReviewPage({ title, subtitle, targetId, targetType = "opportunit
|
|
|
23
23
|
var _a;
|
|
24
24
|
const user = useAtomValue(userAtom);
|
|
25
25
|
const loading = useAtomValue(userLoadingAtom);
|
|
26
|
-
const [review, setReview] = useState((_a = initialReviews.find((r) => r.reviewerUsername === (user === null || user === void 0 ? void 0 : user.username))) !== null && _a !== void 0 ? _a : null);
|
|
26
|
+
const [review, setReview] = useState(Array.isArray(initialReviews) ? ((_a = initialReviews.find((r) => r.reviewerUsername === (user === null || user === void 0 ? void 0 : user.username))) !== null && _a !== void 0 ? _a : null) : null);
|
|
27
27
|
const [reviews, setReviews] = useState(initialReviews);
|
|
28
28
|
const [displayReviews, setDisplayReviews] = useState([]);
|
|
29
|
+
const [page, setPage] = useState(1);
|
|
30
|
+
const [hasMore, setHasMore] = useState(false);
|
|
31
|
+
const [totalCount, setTotalCount] = useState(Array.isArray(initialReviews) ? initialReviews.length : 0);
|
|
29
32
|
const [hoveredRating, setHoveredRating] = useState(null);
|
|
30
33
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
31
34
|
const [isDeleting, setIsDeleting] = useState(false);
|
|
32
35
|
const [isFormInitialized, setIsFormInitialized] = useState(false);
|
|
36
|
+
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
|
33
37
|
const form = useForm({
|
|
34
38
|
resolver: zodResolver(reviewBasicSchema),
|
|
35
39
|
defaultValues: {
|
|
@@ -42,17 +46,25 @@ export function ReviewPage({ title, subtitle, targetId, targetType = "opportunit
|
|
|
42
46
|
if (loading)
|
|
43
47
|
return;
|
|
44
48
|
const fetchData = async () => {
|
|
45
|
-
|
|
49
|
+
// Cast to any to bypass stale type definitions in @myrjfa/state/dist
|
|
50
|
+
const response = await getReviewsById(targetId, targetType, { page: 1, limit: 10 });
|
|
46
51
|
if (response === null || response === void 0 ? void 0 : response.error) {
|
|
47
52
|
console.error("Error: " + response.error);
|
|
48
53
|
}
|
|
49
|
-
else {
|
|
54
|
+
else if (Array.isArray(response.reviews)) {
|
|
50
55
|
setReviews(response.reviews);
|
|
56
|
+
setTotalCount(response.totalCount);
|
|
57
|
+
setHasMore(response.hasMore);
|
|
58
|
+
setPage(1);
|
|
51
59
|
const myReview = response.reviews.find((r) => r.reviewerUsername === (user === null || user === void 0 ? void 0 : user.username));
|
|
52
60
|
setReview(myReview !== null && myReview !== void 0 ? myReview : null);
|
|
53
61
|
form.reset({ rating: reviewCategory === "comment" ? 1 : ((myReview === null || myReview === void 0 ? void 0 : myReview.rating) || 0), comment: (myReview === null || myReview === void 0 ? void 0 : myReview.comment) || "" });
|
|
54
62
|
setIsFormInitialized(true);
|
|
55
63
|
}
|
|
64
|
+
else {
|
|
65
|
+
setReviews([]);
|
|
66
|
+
setIsFormInitialized(true);
|
|
67
|
+
}
|
|
56
68
|
};
|
|
57
69
|
if (!initialReviews || initialReviews.length === 0) {
|
|
58
70
|
fetchData();
|
|
@@ -61,6 +73,27 @@ export function ReviewPage({ title, subtitle, targetId, targetType = "opportunit
|
|
|
61
73
|
setIsFormInitialized(true); // Initialize immediately if already present
|
|
62
74
|
}
|
|
63
75
|
}, [targetId, targetType, user, loading]);
|
|
76
|
+
const handleLoadMore = async () => {
|
|
77
|
+
if (isLoadingMore || !hasMore)
|
|
78
|
+
return;
|
|
79
|
+
setIsLoadingMore(true);
|
|
80
|
+
try {
|
|
81
|
+
const nextPage = page + 1;
|
|
82
|
+
// Cast to any to bypass stale type definitions in @myrjfa/state/dist
|
|
83
|
+
const response = await getReviewsById(targetId, targetType, { page: nextPage, limit: 10 });
|
|
84
|
+
if (response === null || response === void 0 ? void 0 : response.error) {
|
|
85
|
+
console.error("Error: " + response.error);
|
|
86
|
+
}
|
|
87
|
+
else if (Array.isArray(response.reviews)) {
|
|
88
|
+
setReviews([...reviews, ...response.reviews]);
|
|
89
|
+
setPage(nextPage);
|
|
90
|
+
setHasMore(response.hasMore);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
setIsLoadingMore(false);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
64
97
|
useEffect(() => {
|
|
65
98
|
if (review) {
|
|
66
99
|
form.reset({
|
|
@@ -71,7 +104,12 @@ export function ReviewPage({ title, subtitle, targetId, targetType = "opportunit
|
|
|
71
104
|
setIsFormInitialized(true);
|
|
72
105
|
}, [review]);
|
|
73
106
|
useEffect(() => {
|
|
74
|
-
|
|
107
|
+
if (Array.isArray(reviews)) {
|
|
108
|
+
setDisplayReviews(reviews.filter((r) => r.comment));
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
setDisplayReviews([]);
|
|
112
|
+
}
|
|
75
113
|
}, [reviews]);
|
|
76
114
|
const onSubmit = async (data) => {
|
|
77
115
|
setIsSubmitting(true);
|
|
@@ -91,6 +129,7 @@ export function ReviewPage({ title, subtitle, targetId, targetType = "opportunit
|
|
|
91
129
|
if (newReview) {
|
|
92
130
|
setReviews([newReview, ...reviews.filter((r) => r._id !== newReview._id)]);
|
|
93
131
|
setReview(newReview);
|
|
132
|
+
setTotalCount(prev => review ? prev : prev + 1);
|
|
94
133
|
form.reset({ rating: reviewCategory === "comment" ? 1 : newReview.rating, comment: newReview.comment || "" });
|
|
95
134
|
}
|
|
96
135
|
}
|
|
@@ -121,6 +160,7 @@ export function ReviewPage({ title, subtitle, targetId, targetType = "opportunit
|
|
|
121
160
|
else {
|
|
122
161
|
setReviews(reviews.filter((r) => r._id !== review._id));
|
|
123
162
|
setReview(null);
|
|
163
|
+
setTotalCount(prev => Math.max(0, prev - 1));
|
|
124
164
|
form.reset({ rating: reviewCategory === "comment" ? 1 : 0, comment: "" });
|
|
125
165
|
}
|
|
126
166
|
}
|
|
@@ -135,5 +175,5 @@ export function ReviewPage({ title, subtitle, targetId, targetType = "opportunit
|
|
|
135
175
|
if (loading || !isFormInitialized)
|
|
136
176
|
return _jsx(Card, { className: "w-full h-50 flex items-center justify-center", children: "Loading Reviews..." });
|
|
137
177
|
const averageRating = reviews.length ? reviews.reduce((acc, r) => acc + r.rating, 0) / reviews.length : 0;
|
|
138
|
-
return (_jsxs(Card, { className: "w-full mb-10", children: [(title || subtitle) && _jsxs(CardHeader, { children: [title && _jsx(CardTitle, { children: title }), subtitle && _jsx(CardDescription, { children: subtitle })] }), _jsxs(CardContent, { children: [editable && _jsx(Form, { ...form, children: _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), className: "space-y-6 pb-4", children: [_jsx(FormField, { control: form.control, name: "rating", render: ({ field }) => (_jsxs(FormItem, { className: reviewCategory === "comment" ? "hidden" : "", children: [_jsx(FormLabel, { children: "Your Rating" }), _jsx(FormControl, { children: _jsx(StarRating, { value: ratingValue, onChange: (val) => field.onChange(val), onHover: setHoveredRating, interactive: true }) }), _jsx(FormMessage, {})] })) }), _jsx(FormField, { control: form.control, name: "comment", render: ({ field }) => (_jsxs(FormItem, { children: [_jsxs(FormLabel, { children: ["Your ", reviewCategory == "review" ? "Review" : "Comment"] }), _jsx(FormControl, { children: _jsx(Textarea, { placeholder: `Share your ${reviewCategory == "review" ? "experience" : "thoughts"}...`, className: "resize-none", rows: 4, ...field }) }), _jsx(FormMessage, {})] })) }), form.formState.errors.root && (_jsx("p", { className: "text-sm font-medium text-destructive", children: form.formState.errors.root.message })), _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx(Button, { type: "submit", disabled: isSubmitting, className: "w-full sm:w-auto", children: isSubmitting ? (reviewCategory == "review" ? "Submitting... Review" : "Posting... Comment") : (reviewCategory == "review" ? "Submit Review" : "Post Comment") }), review && _jsx(Button, { type: "button", variant: "destructive", className: "w-full sm:w-auto", disabled: isDeleting, onClick: handleDeleteReview, children: isDeleting ? "Deleting..." : reviewCategory == "review" ? "Delete Review" : "Delete Comment" })] })] }) }), displayReviews.length > 0 ? (_jsxs("div", { className: "mt-4", children: [_jsxs("div", { className: "flex justify-between items-center mb-4", children: [_jsxs("h3", { className: "text-lg font-medium", children: [reviewCategory == "review" ? "Reviews" : "Comments", " (", displayReviews.length, ")"] }), reviewCategory == "review" && _jsxs("div", { className: "flex items-center", children: [_jsx("span", { className: "font-medium mr-2", children: averageRating.toFixed(1) }), _jsx(StarRating, { value: Math.round(averageRating), size: 5 })] })] }), _jsx(Separator, { className: "my-4" }), _jsx("div", { className: "space-y-6", children: displayReviews.map((review) => (_jsxs("div", { className: "pb-4", children: [_jsxs("div", { className: "flex items-start justify-between", children: [_jsxs("div", { className: "flex items-center", children: [_jsxs(Avatar, { className: "h-10 w-10 mr-3", children: [_jsx(AvatarImage, { src: review.reviewerPic || "/images/profile_placeholder.webp", alt: review.reviewerUsername }), _jsx(AvatarFallback, { children: review.reviewerUsername.charAt(0) })] }), _jsxs("div", { children: [_jsx("h4", { className: "font-medium", children: review.reviewerUsername }), _jsx("p", { className: "text-sm text-gray-500", children: formatDate(review.createdAt) })] })] }), reviewCategory == "review" && _jsx(StarRating, { value: review.rating, size: 4 })] }), _jsx("p", { className: "mt-2 text-gray-600 whitespace-pre-line", children: review.comment }), _jsx(Separator, { className: "mt-4" })] }, review._id))) })] })) : (_jsxs("div", { className: "text-center justify-center p-5", children: ["No ", reviewCategory == "comment" ? "comments" : "reviews", " yet"] }))] }), displayReviews.length > 0 && _jsx(CardFooter, { children: _jsx("p", { className: "text-sm text-gray-500", children: "Thank you for sharing your feedback. All reviews are moderated and typically appear within 24-48 hours." }) })] }));
|
|
178
|
+
return (_jsxs(Card, { className: "w-full mb-10", children: [(title || subtitle) && _jsxs(CardHeader, { children: [title && _jsx(CardTitle, { children: title }), subtitle && _jsx(CardDescription, { children: subtitle })] }), _jsxs(CardContent, { children: [editable && _jsx(Form, { ...form, children: _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), className: "space-y-6 pb-4", children: [_jsx(FormField, { control: form.control, name: "rating", render: ({ field }) => (_jsxs(FormItem, { className: reviewCategory === "comment" ? "hidden" : "", children: [_jsx(FormLabel, { children: "Your Rating" }), _jsx(FormControl, { children: _jsx(StarRating, { value: ratingValue, onChange: (val) => field.onChange(val), onHover: setHoveredRating, interactive: true }) }), _jsx(FormMessage, {})] })) }), _jsx(FormField, { control: form.control, name: "comment", render: ({ field }) => (_jsxs(FormItem, { children: [_jsxs(FormLabel, { children: ["Your ", reviewCategory == "review" ? "Review" : "Comment"] }), _jsx(FormControl, { children: _jsx(Textarea, { placeholder: `Share your ${reviewCategory == "review" ? "experience" : "thoughts"}...`, className: "resize-none", rows: 4, ...field }) }), _jsx(FormMessage, {})] })) }), form.formState.errors.root && (_jsx("p", { className: "text-sm font-medium text-destructive", children: form.formState.errors.root.message })), _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx(Button, { type: "submit", disabled: isSubmitting, className: "w-full sm:w-auto", children: isSubmitting ? (reviewCategory == "review" ? "Submitting... Review" : "Posting... Comment") : (reviewCategory == "review" ? "Submit Review" : "Post Comment") }), review && _jsx(Button, { type: "button", variant: "destructive", className: "w-full sm:w-auto", disabled: isDeleting, onClick: handleDeleteReview, children: isDeleting ? "Deleting..." : reviewCategory == "review" ? "Delete Review" : "Delete Comment" })] })] }) }), displayReviews.length > 0 ? (_jsxs("div", { className: "mt-4", children: [_jsxs("div", { className: "flex justify-between items-center mb-4", children: [_jsxs("h3", { className: "text-lg font-medium", children: [reviewCategory == "review" ? "Reviews" : "Comments", " (", totalCount || displayReviews.length, ")"] }), reviewCategory == "review" && _jsxs("div", { className: "flex items-center", children: [_jsx("span", { className: "font-medium mr-2", children: averageRating.toFixed(1) }), _jsx(StarRating, { value: Math.round(averageRating), size: 5 })] })] }), _jsx(Separator, { className: "my-4" }), _jsx("div", { className: "space-y-6", children: displayReviews.map((review) => (_jsxs("div", { className: "pb-4", children: [_jsxs("div", { className: "flex items-start justify-between", children: [_jsxs("div", { className: "flex items-center", children: [_jsxs(Avatar, { className: "h-10 w-10 mr-3", children: [_jsx(AvatarImage, { src: review.reviewerPic || "/images/profile_placeholder.webp", alt: review.reviewerUsername }), _jsx(AvatarFallback, { children: review.reviewerUsername.charAt(0) })] }), _jsxs("div", { children: [_jsx("h4", { className: "font-medium", children: review.reviewerUsername }), _jsx("p", { className: "text-sm text-gray-500", children: formatDate(review.createdAt) })] })] }), reviewCategory == "review" && _jsx(StarRating, { value: review.rating, size: 4 })] }), _jsx("p", { className: "mt-2 text-gray-600 whitespace-pre-line", children: review.comment }), _jsx(Separator, { className: "mt-4" })] }, review._id))) }), hasMore && (_jsx("div", { className: "flex justify-center mt-6", children: _jsx(Button, { variant: "outline", onClick: handleLoadMore, disabled: isLoadingMore, className: "w-full sm:w-auto", children: isLoadingMore ? "Loading more..." : "View More" }) }))] })) : (_jsxs("div", { className: "text-center justify-center p-5", children: ["No ", reviewCategory == "comment" ? "comments" : "reviews", " yet"] }))] }), displayReviews.length > 0 && _jsx(CardFooter, { children: _jsx("p", { className: "text-sm text-gray-500", children: "Thank you for sharing your feedback. All reviews are moderated and typically appear within 24-48 hours." }) })] }));
|
|
139
179
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alertdialogclaude.d.ts","sourceRoot":"","sources":["../../src/misc/alertdialogclaude.tsx"],"names":[],"mappings":"AAUA,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAE3D,UAAU,gBAAgB;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;CACzC;AAED,eAAO,MAAM,aAAa,GAAI,QAAQ,GAAG,KAapC,YAAY,EAAE,CAAC;AAEpB,eAAO,MAAM,cAAc,GAAI,QAAQ,GAAG,KAarC,YAAY,EAAE,CAAC;AAEpB,wBAAgB,SAAS,CACrB,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,KAAK,CAAC,YAAY,CAAC,GAC9B,OAAO,CAAC,IAAI,CAAC,CAiCf;AAED,wBAAgB,WAAW,CAAC,EACxB,OAAO,EACP,KAAK,EACL,OAAY,EACZ,MAAM,EACN,YAAY,GACf,EAAE,gBAAgB,
|
|
1
|
+
{"version":3,"file":"alertdialogclaude.d.ts","sourceRoot":"","sources":["../../src/misc/alertdialogclaude.tsx"],"names":[],"mappings":"AAUA,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAE3D,UAAU,gBAAgB;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;CACzC;AAED,eAAO,MAAM,aAAa,GAAI,QAAQ,GAAG,KAapC,YAAY,EAAE,CAAC;AAEpB,eAAO,MAAM,cAAc,GAAI,QAAQ,GAAG,KAarC,YAAY,EAAE,CAAC;AAEpB,wBAAgB,SAAS,CACrB,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,KAAK,CAAC,YAAY,CAAC,GAC9B,OAAO,CAAC,IAAI,CAAC,CAiCf;AAED,wBAAgB,WAAW,CAAC,EACxB,OAAO,EACP,KAAK,EACL,OAAY,EACZ,MAAM,EACN,YAAY,GACf,EAAE,gBAAgB,2CA8DlB;AAyBD,wBAAgB,eAAe,4CA2I9B"}
|
|
@@ -59,7 +59,7 @@ export function showAlert(message, title, actions) {
|
|
|
59
59
|
});
|
|
60
60
|
}
|
|
61
61
|
export function AlertDialog({ message, title, actions = [], isOpen, onOpenChange, }) {
|
|
62
|
-
return (_jsx(AlertDialogPrimitive.Root, { open: isOpen, onOpenChange: onOpenChange, children: _jsxs(AlertDialogPrimitive.Portal, { children: [_jsx(AlertDialogPrimitive.Overlay, { className: "fixed inset-0 z-50 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0" }), _jsxs(AlertDialogPrimitive.Content, { className: "fixed left-1/2 top-1/2 z-50 max-h-[85vh] w-full max-w-md -translate-x-1/2 -translate-y-1/2 rounded-lg border border-neutral-200 bg-white p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-top-[2%] data-[state=open]:slide-in-from-top-[2%] dark:border-neutral-800 dark:bg-neutral-950 sm:rounded-lg md:w-full", children: [_jsxs("div", { className: "flex flex-col space-y-2", children: [title
|
|
62
|
+
return (_jsx(AlertDialogPrimitive.Root, { open: isOpen, onOpenChange: onOpenChange, children: _jsxs(AlertDialogPrimitive.Portal, { children: [_jsx(AlertDialogPrimitive.Overlay, { className: "fixed inset-0 z-50 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0" }), _jsxs(AlertDialogPrimitive.Content, { className: "fixed left-1/2 top-1/2 z-50 max-h-[85vh] w-full max-w-md -translate-x-1/2 -translate-y-1/2 rounded-lg border border-neutral-200 bg-white p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-top-[2%] data-[state=open]:slide-in-from-top-[2%] dark:border-neutral-800 dark:bg-neutral-950 sm:rounded-lg md:w-full", children: [_jsxs("div", { className: "flex flex-col space-y-2", children: [title ? (_jsxs("div", { className: "flex items-center justify-between", children: [_jsx(AlertDialogPrimitive.Title, { className: "text-lg font-semibold text-neutral-900 dark:text-neutral-50 flex-grow text-center", children: title }), _jsx(Button, { variant: "ghost", onClick: () => onOpenChange(false), children: _jsx(X, {}) })] })) : (_jsx(AlertDialogPrimitive.Title, { className: "sr-only", children: "Alert" })), _jsx(AlertDialogPrimitive.Description, { className: "text-sm text-neutral-700 dark:text-neutral-300 text-center", children: message })] }), _jsx("div", { className: cn("mt-6 flex flex-col-reverse gap-2 sm:flex-row", actions.length > 0 ? "sm:justify-end" : "justify-center"), children: actions.length > 0 ? (actions.map((action, index) => (_jsx(AlertDialogButton, { variant: action.variant || 'default', onClick: () => {
|
|
63
63
|
action.action();
|
|
64
64
|
onOpenChange(false);
|
|
65
65
|
}, children: action.label }, index)))) : (_jsx(AlertDialogButton, { onClick: () => onOpenChange(false), children: "OK" })) })] })] }) }));
|
package/dist/misc/command.js
CHANGED
|
@@ -14,7 +14,7 @@ function CommandInput({ className, ...props }) {
|
|
|
14
14
|
return (_jsxs("div", { "data-slot": "command-input-wrapper", className: "flex h-9 items-center gap-2 border-b px-3", children: [_jsx(SearchIcon, { className: "size-4 shrink-0 opacity-50" }), _jsx(CommandPrimitive.Input, { "data-slot": "command-input", className: cn("placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50", className), ...props })] }));
|
|
15
15
|
}
|
|
16
16
|
function CommandList({ className, ...props }) {
|
|
17
|
-
return (_jsx(CommandPrimitive.List, { "data-slot": "command-list", className: cn("max-h-[
|
|
17
|
+
return (_jsx(CommandPrimitive.List, { "data-slot": "command-list", className: cn("max-h-[350px] scroll-py-1 overflow-x-hidden overflow-y-auto touch-pan-y z-[300]", className), ...props }));
|
|
18
18
|
}
|
|
19
19
|
function CommandEmpty({ ...props }) {
|
|
20
20
|
return (_jsx(CommandPrimitive.Empty, { "data-slot": "command-empty", className: "py-6 text-center text-sm", ...props }));
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ActionButtons = ActionButtons;
|
|
7
|
+
const lucide_react_1 = require("lucide-react");
|
|
8
|
+
const react_1 = __importDefault(require("react"));
|
|
9
|
+
const button_1 = require("../misc/button");
|
|
10
|
+
const dropdown_menu_1 = require("../misc/dropdown-menu");
|
|
11
|
+
function ActionButtons({ actions, includeMoreMenu = true, maxDisplayed = 0 }) {
|
|
12
|
+
const displayedActions = actions.slice(0, maxDisplayed);
|
|
13
|
+
const menuActions = includeMoreMenu ? actions.slice(maxDisplayed) : [];
|
|
14
|
+
return (react_1.default.createElement("div", { className: "flex items-center gap-2" },
|
|
15
|
+
displayedActions.map((action, index) => (react_1.default.createElement(button_1.Button, { key: index, variant: action.variant || 'outline', size: "sm", onClick: (e) => {
|
|
16
|
+
e.stopPropagation();
|
|
17
|
+
action.action();
|
|
18
|
+
}, className: 'cursor-pointer' },
|
|
19
|
+
action.icon,
|
|
20
|
+
react_1.default.createElement("span", { className: "ml-1" }, action.label)))),
|
|
21
|
+
menuActions.length > 0 && (react_1.default.createElement(dropdown_menu_1.DropdownMenu, null,
|
|
22
|
+
react_1.default.createElement(dropdown_menu_1.DropdownMenuTrigger, { asChild: true },
|
|
23
|
+
react_1.default.createElement(button_1.Button, { variant: "outline", size: "sm" },
|
|
24
|
+
react_1.default.createElement(lucide_react_1.MoreHorizontal, { className: "h-4 w-4 cursor-pointer" }))),
|
|
25
|
+
react_1.default.createElement(dropdown_menu_1.DropdownMenuContent, { align: "end" }, menuActions.map((action, index) => (react_1.default.createElement(dropdown_menu_1.DropdownMenuItem, { key: index, onClick: (e) => {
|
|
26
|
+
e.stopPropagation();
|
|
27
|
+
action.action();
|
|
28
|
+
} },
|
|
29
|
+
action.icon,
|
|
30
|
+
react_1.default.createElement("span", { className: "ml-2" }, action.label)))))))));
|
|
31
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type UploadType = "image" | "document" | "both";
|
|
2
|
+
interface FileUploadProps {
|
|
3
|
+
label?: string;
|
|
4
|
+
required?: boolean;
|
|
5
|
+
type?: UploadType;
|
|
6
|
+
value?: File | null;
|
|
7
|
+
onChange: (file: File | null) => void;
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function FileUpload({ label, required, type, value, onChange, className, }: FileUploadProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=document-upload-button.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document-upload-button.d.ts","sourceRoot":"","sources":["../../src/misc/document-upload-button.tsx"],"names":[],"mappings":"AAMA,KAAK,UAAU,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,CAAC;AAEhD,UAAU,eAAe;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,KAAK,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,KAAK,IAAI,CAAC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,UAAU,CAAC,EACvB,KAAqB,EACrB,QAAQ,EACR,IAAa,EACb,KAAK,EACL,QAAQ,EACR,SAAS,GACZ,EAAE,eAAe,2CAmEjB"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRef } from "react";
|
|
3
|
+
import { cn } from "@myrjfa/state/dist/lib/utils";
|
|
4
|
+
import { FileUp, ImageIcon, FileText } from "lucide-react";
|
|
5
|
+
import { Input } from "./input";
|
|
6
|
+
import { Label } from "./label";
|
|
7
|
+
export function FileUpload({ label = "Upload File", required, type = "both", value, onChange, className, }) {
|
|
8
|
+
const inputRef = useRef(null);
|
|
9
|
+
const acceptMap = {
|
|
10
|
+
image: "image/*",
|
|
11
|
+
document: "application/pdf",
|
|
12
|
+
both: "application/pdf,image/*",
|
|
13
|
+
};
|
|
14
|
+
const helperTextMap = {
|
|
15
|
+
image: "Image only",
|
|
16
|
+
document: "PDF only",
|
|
17
|
+
both: "PDF or image",
|
|
18
|
+
};
|
|
19
|
+
const Icon = type === "image"
|
|
20
|
+
? ImageIcon
|
|
21
|
+
: type === "document"
|
|
22
|
+
? FileText
|
|
23
|
+
: FileUp;
|
|
24
|
+
return (_jsxs("div", { className: cn("w-full", className), children: [_jsxs(Label, { className: "text-sm font-medium", children: [label, " ", required && _jsx("span", { className: "text-destructive", children: "*" }), _jsxs("span", { className: "text-xs text-muted-foreground ml-2", children: ["(", helperTextMap[type], ")"] })] }), _jsxs("div", { onClick: () => { var _a; return (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, className: cn("mt-2 border-2 border-dashed rounded-lg cursor-pointer transition-all", "flex flex-col items-center justify-center text-center", "hover:border-primary/50",
|
|
25
|
+
// 📱 Responsive sizing
|
|
26
|
+
"p-3"), children: [_jsx(Icon, { className: "w-5 h-5 sm:w-6 sm:h-6 text-muted-foreground mb-1" }), value ? (_jsx("p", { className: "text-xs sm:text-sm text-primary font-medium break-all", children: value.name })) : (_jsx("p", { className: "text-xs sm:text-sm text-muted-foreground", children: "Tap or click to upload" })), _jsx(Input, { ref: inputRef, type: "file", accept: acceptMap[type], className: "hidden", onChange: (e) => { var _a, _b; return onChange((_b = (_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : null); } })] })] }));
|
|
27
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface EmptyStateProps {
|
|
3
|
+
title?: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
icon?: React.ReactNode;
|
|
6
|
+
action?: React.ReactNode;
|
|
7
|
+
}
|
|
8
|
+
export declare function EmptyState({ title, description, icon, action, }: EmptyStateProps): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=empty-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"empty-state.d.ts","sourceRoot":"","sources":["../../src/misc/empty-state.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,UAAU,eAAe;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B;AAED,wBAAgB,UAAU,CAAC,EACvB,KAA0B,EAC1B,WAAW,EACX,IAAI,EACJ,MAAM,GACT,EAAE,eAAe,2CAajB"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Inbox } from 'lucide-react';
|
|
3
|
+
export function EmptyState({ title = 'Nothing here yet', description, icon, action, }) {
|
|
4
|
+
return (_jsxs("div", { className: "flex flex-col items-center justify-center py-16 px-4 text-center gap-3", children: [_jsx("div", { className: "text-muted-foreground", children: icon !== null && icon !== void 0 ? icon : _jsx(Inbox, { className: "w-12 h-12 mx-auto mb-2 opacity-40" }) }), _jsx("h3", { className: "text-lg font-semibold text-foreground", children: title }), description && (_jsx("p", { className: "text-sm text-muted-foreground max-w-xs", children: description })), action && _jsx("div", { className: "mt-2", children: action })] }));
|
|
5
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filter.d.ts","sourceRoot":"","sources":["../../src/misc/filter.tsx"],"names":[],"mappings":"AAsBA,KAAK,WAAW,GAAG;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACzC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACzC,MAAM,CAAC,EAAE,OAAO,CAAA;CACnB,CAAC;AACF,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,EAAE,aACwC,CAAC;AAElF,QAAA,MAAM,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,WAAW,
|
|
1
|
+
{"version":3,"file":"filter.d.ts","sourceRoot":"","sources":["../../src/misc/filter.tsx"],"names":[],"mappings":"AAsBA,KAAK,WAAW,GAAG;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACzC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACzC,MAAM,CAAC,EAAE,OAAO,CAAA;CACnB,CAAC;AACF,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,EAAE,aACwC,CAAC;AAElF,QAAA,MAAM,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,WAAW,CAqHjC,CAAC;AAEF,OAAO,EAAE,MAAM,EAAE,CAAC"}
|
package/dist/misc/filter.js
CHANGED
|
@@ -30,7 +30,7 @@ const Filter = ({ values, label, formType, selectedValue, onChange, others }) =>
|
|
|
30
30
|
}, [open]);
|
|
31
31
|
return (_jsx("div", { className: cn("filter-container", label), children: formType === "select" ? (_jsxs(Select, { value: selectedValue !== null && selectedValue !== void 0 ? selectedValue : "", onValueChange: (val) => onChange(val === "clear" ? null : val), children: [_jsx(SelectTrigger, { className: "w-full bg-transparent hover:bg-muted border-none shadow-none", children: !selectedValue || selectedValue === "clear" || selectedValue[0] == '0'
|
|
32
32
|
? `Choose ${label.toLowerCase()}...`
|
|
33
|
-
: selectedValue }), _jsxs(SelectContent, { className: "w-full z-
|
|
33
|
+
: selectedValue }), _jsxs(SelectContent, { className: "w-full z-[301]", children: [_jsx(SelectItem, { value: "clear", children: _jsx("em", { children: "Clear" }) }), data.map((option) => (_jsx(SelectItem, { value: option, children: option }, option)))] })] })) : (_jsxs(Popover, { open: open, onOpenChange: setOpen, modal: false, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { ref: triggerRef, variant: "ghost", role: "combobox", className: "filter-box justify-between w-full", children: [selectedValue || `Select ${label.toLowerCase()}...`, _jsx(ChevronDown, { className: "ml-2 h-4 w-4 shrink-0 opacity-50" })] }) }), _jsxs(PopoverContent, { className: "p-0 z-[310] pointer-events-auto overflow-hidden", style: { width: popoverWidth > 0 ? `${popoverWidth}px` : 'auto', minWidth: 'min(300px, 90vw)' }, align: "start", onWheel: (e) => e.stopPropagation(), children: [_jsxs(Command, { children: [_jsx(CommandInput, { placeholder: `Search ${label.toLowerCase()}...` }), _jsx(CommandEmpty, { children: "No results found." }), _jsx(CommandList, { children: data.map((option) => (_jsxs(CommandItem, { value: option, onSelect: () => {
|
|
34
34
|
onChange(option);
|
|
35
35
|
setOpen(false);
|
|
36
36
|
}, children: [_jsx(Check, { className: cn("mr-2 h-4 w-4", selectedValue === option ? "opacity-100" : "opacity-0") }), option] }, option))) })] }), others &&
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface CompressOptions {
|
|
2
|
+
quality?: number;
|
|
3
|
+
maxWidth?: number;
|
|
4
|
+
maxHeight?: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Compress an image File using Canvas API.
|
|
8
|
+
* Returns a new File object with the same name but compressed content.
|
|
9
|
+
*/
|
|
10
|
+
export declare function compressImage(file: File, options?: CompressOptions): Promise<File>;
|
|
11
|
+
export interface ImageCompressorProps {
|
|
12
|
+
enableCompression?: boolean;
|
|
13
|
+
quality?: number;
|
|
14
|
+
maxWidth?: number;
|
|
15
|
+
maxHeight?: number;
|
|
16
|
+
accept?: string;
|
|
17
|
+
multiple?: boolean;
|
|
18
|
+
onChange: (files: File[]) => void;
|
|
19
|
+
children?: React.ReactNode;
|
|
20
|
+
className?: string;
|
|
21
|
+
id?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* #26 — Image Compressor Input Component
|
|
25
|
+
* Drop-in replacement for a file input that optionally compresses images
|
|
26
|
+
* before passing them to the onChange handler.
|
|
27
|
+
*/
|
|
28
|
+
export declare function ImageCompressorInput({ enableCompression, quality, maxWidth, maxHeight, accept, multiple, onChange, children, className, id, }: ImageCompressorProps): import("react/jsx-runtime").JSX.Element;
|
|
29
|
+
//# sourceMappingURL=image-compressor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-compressor.d.ts","sourceRoot":"","sources":["../../src/misc/image-compressor.tsx"],"names":[],"mappings":"AAIA,MAAM,WAAW,eAAe;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAC/B,IAAI,EAAE,IAAI,EACV,OAAO,GAAE,eAAoB,GAC9B,OAAO,CAAC,IAAI,CAAC,CAkDf;AAED,MAAM,WAAW,oBAAoB;IACjC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IAClC,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,EAAE,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,EACjC,iBAAwB,EACxB,OAAa,EACb,QAAe,EACf,SAAgB,EAChB,MAAoD,EACpD,QAAgB,EAChB,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,EAAE,GACL,EAAE,oBAAoB,2CAmCtB"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useRef, useCallback } from 'react';
|
|
4
|
+
/**
|
|
5
|
+
* Compress an image File using Canvas API.
|
|
6
|
+
* Returns a new File object with the same name but compressed content.
|
|
7
|
+
*/
|
|
8
|
+
export async function compressImage(file, options = {}) {
|
|
9
|
+
const { quality = 0.8, maxWidth = 1920, maxHeight = 1920 } = options;
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
const img = new Image();
|
|
12
|
+
const url = URL.createObjectURL(file);
|
|
13
|
+
img.onload = () => {
|
|
14
|
+
URL.revokeObjectURL(url);
|
|
15
|
+
let { width, height } = img;
|
|
16
|
+
if (width > maxWidth) {
|
|
17
|
+
height = Math.round((height * maxWidth) / width);
|
|
18
|
+
width = maxWidth;
|
|
19
|
+
}
|
|
20
|
+
if (height > maxHeight) {
|
|
21
|
+
width = Math.round((width * maxHeight) / height);
|
|
22
|
+
height = maxHeight;
|
|
23
|
+
}
|
|
24
|
+
const canvas = document.createElement('canvas');
|
|
25
|
+
canvas.width = width;
|
|
26
|
+
canvas.height = height;
|
|
27
|
+
const ctx = canvas.getContext('2d');
|
|
28
|
+
if (!ctx) {
|
|
29
|
+
resolve(file); // fallback: return original
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
ctx.drawImage(img, 0, 0, width, height);
|
|
33
|
+
canvas.toBlob((blob) => {
|
|
34
|
+
if (!blob) {
|
|
35
|
+
resolve(file); // fallback
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
resolve(new File([blob], file.name, { type: 'image/webp', lastModified: Date.now() }));
|
|
39
|
+
}, 'image/webp', quality);
|
|
40
|
+
};
|
|
41
|
+
img.onerror = () => {
|
|
42
|
+
URL.revokeObjectURL(url);
|
|
43
|
+
reject(new Error(`Failed to load image: ${file.name}`));
|
|
44
|
+
};
|
|
45
|
+
img.src = url;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* #26 — Image Compressor Input Component
|
|
50
|
+
* Drop-in replacement for a file input that optionally compresses images
|
|
51
|
+
* before passing them to the onChange handler.
|
|
52
|
+
*/
|
|
53
|
+
export function ImageCompressorInput({ enableCompression = true, quality = 0.8, maxWidth = 1920, maxHeight = 1920, accept = 'image/jpeg,image/jpg,image/png,image/webp', multiple = false, onChange, children, className, id, }) {
|
|
54
|
+
const inputRef = useRef(null);
|
|
55
|
+
const handleChange = useCallback(async (e) => {
|
|
56
|
+
const rawFiles = Array.from(e.target.files || []);
|
|
57
|
+
if (!rawFiles.length)
|
|
58
|
+
return;
|
|
59
|
+
if (!enableCompression) {
|
|
60
|
+
onChange(rawFiles);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const compressed = await Promise.all(rawFiles.map(f => compressImage(f, { quality, maxWidth, maxHeight }).catch(() => f)));
|
|
64
|
+
onChange(compressed);
|
|
65
|
+
// Reset input so the same file can be re-selected
|
|
66
|
+
if (inputRef.current)
|
|
67
|
+
inputRef.current.value = '';
|
|
68
|
+
}, [enableCompression, quality, maxWidth, maxHeight, onChange]);
|
|
69
|
+
return (_jsxs("label", { className: className, style: { cursor: 'pointer' }, children: [children, _jsx("input", { ref: inputRef, id: id, type: "file", accept: accept, multiple: multiple, onChange: handleChange, style: { display: 'none' } })] }));
|
|
70
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
interface ImageCropperProps {
|
|
3
|
+
image: string;
|
|
4
|
+
onCancel: () => void;
|
|
5
|
+
onCropComplete: (croppedImage: Blob) => void;
|
|
6
|
+
open: boolean;
|
|
7
|
+
}
|
|
8
|
+
declare const ImageCropper: React.FC<ImageCropperProps>;
|
|
9
|
+
export default ImageCropper;
|
|
10
|
+
//# sourceMappingURL=image-cropper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-cropper.d.ts","sourceRoot":"","sources":["../../src/misc/image-cropper.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAgC,MAAM,OAAO,CAAC;AAMrD,UAAU,iBAAiB;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,cAAc,EAAE,CAAC,YAAY,EAAE,IAAI,KAAK,IAAI,CAAC;IAC7C,IAAI,EAAE,OAAO,CAAC;CACjB;AAED,QAAA,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAgG7C,CAAC;AAEF,eAAe,YAAY,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useCallback } from "react";
|
|
4
|
+
import Cropper from "react-easy-crop";
|
|
5
|
+
import { Button } from "./button";
|
|
6
|
+
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "./dialog";
|
|
7
|
+
import { Slider } from "./slider";
|
|
8
|
+
const ImageCropper = ({ image, onCancel, onCropComplete, open }) => {
|
|
9
|
+
const [crop, setCrop] = useState({ x: 0, y: 0 });
|
|
10
|
+
const [zoom, setZoom] = useState(1);
|
|
11
|
+
const [croppedAreaPixels, setCroppedAreaPixels] = useState(null);
|
|
12
|
+
const onCropChange = (crop) => {
|
|
13
|
+
setCrop(crop);
|
|
14
|
+
};
|
|
15
|
+
const onZoomChange = (zoom) => {
|
|
16
|
+
setZoom(zoom);
|
|
17
|
+
};
|
|
18
|
+
const onCropCompleteCallback = useCallback((_, croppedAreaPixels) => {
|
|
19
|
+
setCroppedAreaPixels(croppedAreaPixels);
|
|
20
|
+
}, []);
|
|
21
|
+
const createCroppedImage = async () => {
|
|
22
|
+
if (!croppedAreaPixels || !image)
|
|
23
|
+
return;
|
|
24
|
+
const img = new Image();
|
|
25
|
+
img.src = image;
|
|
26
|
+
await new Promise((resolve) => {
|
|
27
|
+
img.onload = resolve;
|
|
28
|
+
});
|
|
29
|
+
const canvas = document.createElement("canvas");
|
|
30
|
+
const ctx = canvas.getContext("2d");
|
|
31
|
+
if (!ctx)
|
|
32
|
+
return;
|
|
33
|
+
canvas.width = croppedAreaPixels.width;
|
|
34
|
+
canvas.height = croppedAreaPixels.height;
|
|
35
|
+
ctx.drawImage(img, croppedAreaPixels.x, croppedAreaPixels.y, croppedAreaPixels.width, croppedAreaPixels.height, 0, 0, croppedAreaPixels.width, croppedAreaPixels.height);
|
|
36
|
+
canvas.toBlob((blob) => {
|
|
37
|
+
if (blob) {
|
|
38
|
+
onCropComplete(blob);
|
|
39
|
+
}
|
|
40
|
+
}, "image/jpeg");
|
|
41
|
+
};
|
|
42
|
+
return (_jsx(Dialog, { open: open, onOpenChange: (isOpen) => !isOpen && onCancel(), children: _jsxs(DialogContent, { className: "sm:max-w-[500px] z-[300]", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Crop Profile Picture" }) }), _jsx("div", { className: "relative h-[300px] w-full bg-gray-100 rounded-md overflow-hidden", children: _jsx(Cropper, { image: image, crop: crop, zoom: zoom, aspect: 1, cropShape: "round", showGrid: false, onCropChange: onCropChange, onCropComplete: onCropCompleteCallback, onZoomChange: onZoomChange }) }), _jsx("div", { className: "py-4", children: _jsxs("div", { className: "flex items-center gap-4", children: [_jsx("span", { className: "text-sm font-medium", children: "Zoom" }), _jsx(Slider, { value: [zoom], min: 1, max: 5, step: 0.1, onValueChange: (values) => { var _a; return setZoom((_a = values[0]) !== null && _a !== void 0 ? _a : 1); }, className: "flex-1" })] }) }), _jsxs(DialogFooter, { className: "flex gap-2", children: [_jsx(Button, { variant: "outline", onClick: onCancel, children: "Cancel" }), _jsx(Button, { onClick: createCroppedImage, children: "Set Profile Picture" })] })] }) }));
|
|
43
|
+
};
|
|
44
|
+
export default ImageCropper;
|
package/dist/misc/input.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../src/misc/input.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,iBAAS,KAAK,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../src/misc/input.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,iBAAS,KAAK,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,2CAgB1E;AAED,OAAO,EAAE,KAAK,EAAE,CAAA"}
|
package/dist/misc/input.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from "@myrjfa/state";
|
|
3
3
|
function Input({ className, type, ...props }) {
|
|
4
|
-
return (_jsx("input", { type: type, "data-slot": "input", className: cn("border-input file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]", "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", className), ...props }));
|
|
4
|
+
return (_jsx("input", { type: type, "data-slot": "input", onWheel: (e) => e.currentTarget.blur(), className: cn("border-input file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]", "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", "[&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none", className), ...props }));
|
|
5
5
|
}
|
|
6
6
|
export { Input };
|