feature/notification #12
@@ -13,7 +13,15 @@
|
||||
"common_per_page": "Show",
|
||||
"common_select_page_size": "Select page size",
|
||||
"common_no_list": "Currently there is no data!",
|
||||
"common_no_notify": "Không có thống báo",
|
||||
"common_is_required": "{field} is required.",
|
||||
"common_time_ago_second": "{value} giây trước",
|
||||
"common_time_ago_minute": "{value} phút trước",
|
||||
"common_time_ago_hour": "{value} giờ trước",
|
||||
"common_time_ago_day": "{value} ngày trước",
|
||||
"common_time_ago_week": "{value} tuần trước",
|
||||
"common_time_ago_month": "{value} tháng trước",
|
||||
"common_time_ago_year": "{value} năm trước",
|
||||
"role_tags": [
|
||||
{
|
||||
"match": {
|
||||
@@ -170,23 +178,42 @@
|
||||
"houses_page_message_house_not_found": "House not found!",
|
||||
"houses_page_message_update_house_success": "Updated house successfully!",
|
||||
"houses_page_message_delete_house_success": "Delete house successfully!",
|
||||
"houses_page_message_invite_member_success": "Invite member successfully!",
|
||||
"houses_page_message_cancel_invitation_success": "Cancel invitation successfully!",
|
||||
"houses_page_house_active_btn": "Active",
|
||||
"houses_user_page_message_active_house_success": "Active \"<b>{house}</b>\" successfully!",
|
||||
"houses_user_page_block_action_title": "Action",
|
||||
"houses_user_page_action_invite_user": "Invite member",
|
||||
"houses_user_page_invite_label_to": "To",
|
||||
"houses_user_page_invite_label_status": "Status",
|
||||
"invitation_not_found": "Invitation not found!",
|
||||
"notification_page_notify_not_found": "Notification not found!",
|
||||
"invite_status": [
|
||||
{
|
||||
"match": {
|
||||
"status=pending": "Pending",
|
||||
"status=accept": "Accept",
|
||||
"status=reject": "Reject",
|
||||
"status=accepted": "Accept",
|
||||
"status=rejected": "Reject",
|
||||
"status=expired": "Expired",
|
||||
"status=canceled": "Cancel"
|
||||
}
|
||||
}
|
||||
],
|
||||
"templates_title_notification": [
|
||||
{
|
||||
"match": {
|
||||
"title=INVITATION_HOUSE": "Invite to join house"
|
||||
}
|
||||
}
|
||||
],
|
||||
"templates_message_notification": [
|
||||
{
|
||||
"match": {
|
||||
"message=INVITATION_HOUSE": "You have been invited to join house: {name}, do you accept?"
|
||||
}
|
||||
}
|
||||
],
|
||||
"notification_page_message_invitation_success": "You have been invited to join house!",
|
||||
"backend_message": [
|
||||
{
|
||||
"match": {
|
||||
@@ -197,7 +224,8 @@
|
||||
"code=BANNED_USER": "Your account get banned, please contact administrator for more information!",
|
||||
"code=VALIDATION_ERROR": "Some field value invalid!",
|
||||
"code=USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION": "User is not a member of the house",
|
||||
"code=USER_IS_ALREADY_INVITED_TO_THIS_ORGANIZATION": "This member has already been invited, waiting for the member to join!"
|
||||
"code=USER_IS_ALREADY_INVITED_TO_THIS_ORGANIZATION": "This member has already been invited, waiting for the member to join!",
|
||||
"code=INVITATION_NOT_FOUND": "Invitation not found!"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -13,7 +13,15 @@
|
||||
"common_per_page": "Hiển thị",
|
||||
"common_select_page_size": "Chọn số lượng",
|
||||
"common_no_list": "Hiện tại chưa có dữ liệu nào!",
|
||||
"common_no_notify": "Không có thống báo",
|
||||
"common_is_required": "{field} là bắt buộc.",
|
||||
"common_time_ago_second": "{value} giây trước",
|
||||
"common_time_ago_minute": "{value} phút trước",
|
||||
"common_time_ago_hour": "{value} giờ trước",
|
||||
"common_time_ago_day": "{value} ngày trước",
|
||||
"common_time_ago_week": "{value} tuần trước",
|
||||
"common_time_ago_month": "{value} tháng trước",
|
||||
"common_time_ago_year": "{value} năm trước",
|
||||
"role_tags": [
|
||||
{
|
||||
"match": {
|
||||
@@ -50,6 +58,8 @@
|
||||
"ui_ban_btn": "Khóa",
|
||||
"ui_unban_btn": "Mở khóa",
|
||||
"ui_invite_btn": "Mời",
|
||||
"ui_agree_btn": "Đồng ý",
|
||||
"ui_reject_btn": "Từ chối",
|
||||
"ui_update_password_btn": "Đặt lại mật khẩu",
|
||||
"ui_change_role_btn": "Đặt lại quyền hạn",
|
||||
"ui_edit_user_btn": "Chỉnh sửa người dùng",
|
||||
@@ -172,23 +182,43 @@
|
||||
"houses_page_message_house_not_found": "Không tìm thấy nhà này!",
|
||||
"houses_page_message_update_house_success": "Cập nhật nhà thành công!",
|
||||
"houses_page_message_delete_house_success": "Xóa nhà thành công!",
|
||||
"houses_page_message_invite_member_success": "Mời thành viên thành công!",
|
||||
"houses_page_message_cancel_invitation_success": "Hủy lời mời thành công!",
|
||||
"houses_page_house_active_btn": "Kích hoạt",
|
||||
"houses_user_page_message_active_house_success": "Kích hoạt \"<b>{house}</b>\" thành công!",
|
||||
"houses_user_page_block_action_title": "Hành động",
|
||||
"houses_user_page_action_invite_user": "Mời thành viên",
|
||||
"houses_user_page_invite_label_to": "Đến",
|
||||
"houses_user_page_invite_label_status": "Trạng thái",
|
||||
"invitation_not_found": "Không tìm thấy lời mời!",
|
||||
"notification_page_notify_not_found": "Không tìm thấy thống báo!",
|
||||
"invite_status": [
|
||||
{
|
||||
"match": {
|
||||
"status=pending": "Đang chờ",
|
||||
"status=accept": "Đồng ý",
|
||||
"status=reject": "Không đồng ý",
|
||||
"status=accepted": "Đồng ý",
|
||||
"status=rejected": "Không đồng ý",
|
||||
"status=expired": "Hết hạn",
|
||||
"status=canceled": "Đã hủy"
|
||||
}
|
||||
}
|
||||
],
|
||||
"templates_title_notification": [
|
||||
{
|
||||
"match": {
|
||||
"title=INVITATION_HOUSE": "Mời tham gia nhà"
|
||||
}
|
||||
}
|
||||
],
|
||||
"templates_message_notification": [
|
||||
{
|
||||
"match": {
|
||||
"message=INVITATION_HOUSE": "mời bạn tham gia nhà: {name}, bạn có đồng ý không?"
|
||||
}
|
||||
}
|
||||
],
|
||||
"notification_page_message_invitation_success": "Bạn đã đồng ý tham gia nhà!",
|
||||
"notification_page_message_invitation_rejected": "Bạn đã từ chối tham gia nhà!",
|
||||
"backend_message": [
|
||||
{
|
||||
"match": {
|
||||
@@ -199,7 +229,8 @@
|
||||
"code=BANNED_USER": "Bạn đã bị quản trị viên khóa tài khoản, hãy liên hệ quản trị viên để tìm hiểu thêm!",
|
||||
"code=VALIDATION_ERROR": "Có giá trị không hợp lệ!",
|
||||
"code=USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION": "Người dùng này không phải thành viên nhà này",
|
||||
"code=USER_IS_ALREADY_INVITED_TO_THIS_ORGANIZATION": "Thành viên này đã được mời rồi, còn đang đợi thành viên đồng ý!"
|
||||
"code=USER_IS_ALREADY_INVITED_TO_THIS_ORGANIZATION": "Thành viên này đã được mời rồi, còn đang đợi thành viên đồng ý!",
|
||||
"code=INVITATION_NOT_FOUND": "Không tìm thấy lời mời!"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"format": "prettier",
|
||||
"check": "prettier --write . && eslint --fix",
|
||||
"post-cta-init": "npx create-db@latest",
|
||||
"db:reset": "dotenv -e .env.local -- prisma migrate reset",
|
||||
"db:generate": "dotenv -e .env.local -- prisma generate",
|
||||
"db:push": "dotenv -e .env.local -- prisma db push",
|
||||
"db:migrate": "dotenv -e .env.local -- prisma migrate dev",
|
||||
|
||||
54
prisma/migrations/20260214113407_notification/migration.sql
Normal file
54
prisma/migrations/20260214113407_notification/migration.sql
Normal file
@@ -0,0 +1,54 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "account" ALTER COLUMN "createdAt" SET DATA TYPE TIMESTAMPTZ,
|
||||
ALTER COLUMN "updatedAt" SET DATA TYPE TIMESTAMPTZ;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "audit" ALTER COLUMN "createdAt" SET DATA TYPE TIMESTAMPTZ;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "invitation" ALTER COLUMN "expiresAt" SET DATA TYPE TIMESTAMPTZ,
|
||||
ALTER COLUMN "createdAt" SET DATA TYPE TIMESTAMPTZ;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "member" ALTER COLUMN "createdAt" SET DATA TYPE TIMESTAMPTZ;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "organization" ALTER COLUMN "createdAt" SET DATA TYPE TIMESTAMPTZ;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "session" ALTER COLUMN "expiresAt" SET DATA TYPE TIMESTAMPTZ,
|
||||
ALTER COLUMN "createdAt" SET DATA TYPE TIMESTAMPTZ,
|
||||
ALTER COLUMN "updatedAt" SET DATA TYPE TIMESTAMPTZ;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "setting" ALTER COLUMN "createdAt" SET DATA TYPE TIMESTAMPTZ,
|
||||
ALTER COLUMN "updatedAt" SET DATA TYPE TIMESTAMPTZ;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "user" ALTER COLUMN "createdAt" SET DATA TYPE TIMESTAMPTZ,
|
||||
ALTER COLUMN "updatedAt" SET DATA TYPE TIMESTAMPTZ;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "verification" ALTER COLUMN "createdAt" SET DATA TYPE TIMESTAMPTZ,
|
||||
ALTER COLUMN "updatedAt" SET DATA TYPE TIMESTAMPTZ;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "notification" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"message" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL DEFAULT 'system',
|
||||
"link" TEXT,
|
||||
"metadata" TEXT,
|
||||
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"readAt" TIMESTAMPTZ,
|
||||
|
||||
CONSTRAINT "notification_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "notification_userId_idx" ON "notification"("userId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "notification" ADD CONSTRAINT "notification_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -14,11 +14,12 @@ model User {
|
||||
email String
|
||||
emailVerified Boolean @default(false)
|
||||
image String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
createdAt DateTime @default(now()) @db.Timestamptz
|
||||
updatedAt DateTime @updatedAt @db.Timestamptz
|
||||
sessions Session[]
|
||||
accounts Account[]
|
||||
audit Audit[]
|
||||
notification Notification[]
|
||||
|
||||
role String?
|
||||
banned Boolean? @default(false)
|
||||
@@ -34,10 +35,10 @@ model User {
|
||||
|
||||
model Session {
|
||||
id String @id @default(uuid())
|
||||
expiresAt DateTime
|
||||
expiresAt DateTime @db.Timestamptz
|
||||
token String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
createdAt DateTime @default(now()) @db.Timestamptz
|
||||
updatedAt DateTime @updatedAt @db.Timestamptz
|
||||
ipAddress String?
|
||||
userAgent String?
|
||||
userId String
|
||||
@@ -65,8 +66,8 @@ model Account {
|
||||
refreshTokenExpiresAt DateTime?
|
||||
scope String?
|
||||
password String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
createdAt DateTime @default(now()) @db.Timestamptz
|
||||
updatedAt DateTime @updatedAt @db.Timestamptz
|
||||
|
||||
@@index([userId])
|
||||
@@map("account")
|
||||
@@ -77,8 +78,8 @@ model Verification {
|
||||
identifier String
|
||||
value String
|
||||
expiresAt DateTime
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
createdAt DateTime @default(now()) @db.Timestamptz
|
||||
updatedAt DateTime @updatedAt @db.Timestamptz
|
||||
|
||||
@@index([identifier])
|
||||
@@map("verification")
|
||||
@@ -89,7 +90,7 @@ model Organization {
|
||||
name String
|
||||
slug String
|
||||
logo String?
|
||||
createdAt DateTime
|
||||
createdAt DateTime @db.Timestamptz
|
||||
metadata String?
|
||||
members Member[]
|
||||
invitations Invitation[]
|
||||
@@ -107,7 +108,7 @@ model Member {
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
role String @default("member")
|
||||
createdAt DateTime
|
||||
createdAt DateTime @db.Timestamptz
|
||||
|
||||
@@index([organizationId])
|
||||
@@index([userId])
|
||||
@@ -121,8 +122,8 @@ model Invitation {
|
||||
email String
|
||||
role String?
|
||||
status String @default("pending")
|
||||
expiresAt DateTime
|
||||
createdAt DateTime @default(now())
|
||||
expiresAt DateTime @db.Timestamptz
|
||||
createdAt DateTime @default(now()) @db.Timestamptz
|
||||
inviterId String
|
||||
user User @relation(fields: [inviterId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@ -138,8 +139,8 @@ model Setting {
|
||||
description String
|
||||
relation String @default("admin")
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
createdAt DateTime @default(now()) @db.Timestamptz
|
||||
updatedAt DateTime @updatedAt @db.Timestamptz
|
||||
|
||||
@@map("setting")
|
||||
}
|
||||
@@ -152,9 +153,29 @@ model Audit {
|
||||
recordId String
|
||||
oldValue String?
|
||||
newValue String?
|
||||
createdAt DateTime @default(now())
|
||||
createdAt DateTime @default(now()) @db.Timestamptz
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@map("audit")
|
||||
}
|
||||
|
||||
model Notification {
|
||||
id String @id @default(uuid())
|
||||
userId String
|
||||
|
||||
title String
|
||||
message String
|
||||
type String @default("system")
|
||||
|
||||
link String?
|
||||
metadata String?
|
||||
|
||||
createdAt DateTime @default(now()) @db.Timestamptz
|
||||
readAt DateTime? @db.Timestamptz
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([userId])
|
||||
@@map("notification")
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
"baseLocale": "en",
|
||||
"locales": ["en", "vi"],
|
||||
"modules": [
|
||||
"https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@4/dist/index.js",
|
||||
"https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@2/dist/index.js"
|
||||
"https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@latest/dist/index.js",
|
||||
"https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@latest/dist/index.js"
|
||||
],
|
||||
"plugin.inlang.messageFormat": {
|
||||
"pathPattern": "./messages/{locale}.json"
|
||||
|
||||
@@ -1,19 +1,7 @@
|
||||
import { Separator } from '@base-ui/react/separator';
|
||||
import { m } from '@paraglide/messages';
|
||||
import { BellIcon } from '@phosphor-icons/react';
|
||||
import { Badge } from '@ui/badge';
|
||||
import { Button } from '@ui/button';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@ui/dropdown-menu';
|
||||
import { SidebarTrigger } from '@ui/sidebar';
|
||||
import { useAuth } from './auth/auth-provider';
|
||||
import Notification from './Notification';
|
||||
import RouterBreadcrumb from './sidebar/router-breadcrumb';
|
||||
|
||||
export default function Header() {
|
||||
@@ -30,48 +18,7 @@ export default function Header() {
|
||||
/>
|
||||
<RouterBreadcrumb />
|
||||
</div>
|
||||
<div className="flex mr-2">
|
||||
{session?.user && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button size="lg" variant="ghost" className="relative">
|
||||
<BellIcon size={32} />
|
||||
{false && (
|
||||
<Badge
|
||||
variant="destructive"
|
||||
className="absolute -top-1 -right-1 h-5 w-5 flex items-center justify-center p-0 text-xs"
|
||||
>
|
||||
0
|
||||
</Badge>
|
||||
)}
|
||||
<span className="sr-only">Notifications</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-sm min-w-56 rounded-lg">
|
||||
<DropdownMenuLabel className="font-bold text-black">
|
||||
{m.ui_label_notifications()}
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
<div className="flex flex-col gap-1">
|
||||
<p className="text-sm font-medium">System</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
1 hour ago
|
||||
</p>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
{m.ui_view_all_notifications()}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex mr-2">{session?.user && <Notification />}</div>
|
||||
</header>
|
||||
</>
|
||||
);
|
||||
|
||||
83
src/components/Notification.tsx
Normal file
83
src/components/Notification.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import { notificationQueries } from '@/service/queries';
|
||||
import { formatTimeAgo } from '@/utils/helper';
|
||||
import { cn } from '@lib/utils';
|
||||
import { m } from '@paraglide/messages';
|
||||
import { BellIcon } from '@phosphor-icons/react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Link } from '@tanstack/react-router';
|
||||
import { Button } from '@ui/button';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@ui/dropdown-menu';
|
||||
import { Item, ItemContent, ItemDescription, ItemTitle } from './ui/item';
|
||||
|
||||
const Notification = () => {
|
||||
const { data: notifications } = useQuery(notificationQueries.topFive());
|
||||
|
||||
if (!notifications) return null;
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
size="icon-lg"
|
||||
variant="ghost"
|
||||
className="relative rounded-full"
|
||||
>
|
||||
<BellIcon
|
||||
size={32}
|
||||
className={cn('origin-top', { 'animate-bell-ring': true })}
|
||||
/>
|
||||
{true && (
|
||||
<span className="absolute top-1 right-1 rounded-full w-2 h-2 bg-red-600"></span>
|
||||
)}
|
||||
<span className="sr-only">{m.ui_label_notifications()}</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-sm min-w-56 rounded-lg">
|
||||
<DropdownMenuLabel className="font-bold text-black">
|
||||
{m.ui_label_notifications()}
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
{notifications.map((notify) => {
|
||||
return (
|
||||
<DropdownMenuItem key={notify.id}>
|
||||
<Item className="p-0">
|
||||
<ItemContent>
|
||||
<ItemTitle className="text-sm">
|
||||
{m.templates_title_notification({
|
||||
title: notify.title as Parameters<
|
||||
typeof m.templates_title_notification
|
||||
>[0]['title'],
|
||||
})}
|
||||
</ItemTitle>
|
||||
<ItemDescription>
|
||||
{formatTimeAgo(new Date(notify.createdAt))}
|
||||
</ItemDescription>
|
||||
</ItemContent>
|
||||
</Item>
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
})}
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem asChild>
|
||||
<Link to="/management/notifications" className="cursor-pointer">
|
||||
{m.ui_view_all_notifications()}
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
};
|
||||
|
||||
export default Notification;
|
||||
@@ -38,7 +38,7 @@ const UserInviteMemberForm = ({ onSubmit }: FormProps) => {
|
||||
});
|
||||
onSubmit(false);
|
||||
refetch();
|
||||
toast.success(m.houses_page_message_create_house_success(), {
|
||||
toast.success(m.houses_page_message_invite_member_success(), {
|
||||
richColors: true,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import useHasPermission from '@/hooks/use-has-permission';
|
||||
import { cancelInvitation } from '@/service/house.api';
|
||||
import { INVITE_STATUS } from '@/types/enum';
|
||||
import { authClient } from '@lib/auth-client';
|
||||
@@ -23,13 +24,17 @@ type InvitationListProps = {
|
||||
|
||||
const CurrentUserInvitationList = ({ activeHouse }: InvitationListProps) => {
|
||||
const { refetch } = authClient.useActiveOrganization();
|
||||
const { hasPermission, isLoading } = useHasPermission(
|
||||
'invitation',
|
||||
'cancel',
|
||||
true,
|
||||
);
|
||||
|
||||
const { mutate: cancelInvitationMutation } = useMutation({
|
||||
mutationFn: cancelInvitation,
|
||||
onSuccess: () => {
|
||||
refetch();
|
||||
// _setOpen(false);
|
||||
toast.success(m.houses_page_message_delete_house_success(), {
|
||||
toast.success(m.houses_page_message_cancel_invitation_success(), {
|
||||
richColors: true,
|
||||
});
|
||||
},
|
||||
@@ -66,7 +71,7 @@ const CurrentUserInvitationList = ({ activeHouse }: InvitationListProps) => {
|
||||
<TableBody>
|
||||
{activeHouse.invitations.length > 0 ? (
|
||||
activeHouse.invitations.map((item) => (
|
||||
<TableRow>
|
||||
<TableRow key={item.id}>
|
||||
<TableCell>
|
||||
<Item>
|
||||
<ItemContent>
|
||||
@@ -87,7 +92,7 @@ const CurrentUserInvitationList = ({ activeHouse }: InvitationListProps) => {
|
||||
</TableCell>
|
||||
<TableCell className="p-6">
|
||||
<div className="flex justify-end gap-2">
|
||||
{item.status !== INVITE_STATUS.CANCELED && (
|
||||
{item.status === INVITE_STATUS.PENDING && hasPermission && (
|
||||
<Button
|
||||
variant="outline"
|
||||
className="cursor-pointer w-20 py-4"
|
||||
|
||||
@@ -18,7 +18,7 @@ type ActionProps = {};
|
||||
|
||||
const InviteUserAction = ({}: ActionProps) => {
|
||||
const { hasPermission, isLoading } = useHasPermission(
|
||||
'house',
|
||||
'invitation',
|
||||
'create',
|
||||
true,
|
||||
);
|
||||
|
||||
59
src/components/notification/notification-item.tsx
Normal file
59
src/components/notification/notification-item.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import { NOTIFICATION_TYPE } from '@/types/enum';
|
||||
import { cn } from '@lib/utils';
|
||||
import { m } from '@paraglide/messages';
|
||||
import { Item, ItemHeader } from '@ui/item';
|
||||
import { cva, VariantProps } from 'class-variance-authority';
|
||||
import NotificationInvitation from './notification-type/invitation';
|
||||
|
||||
type NotifyProps = {
|
||||
notify: NotificationWithUser;
|
||||
};
|
||||
|
||||
const notifyVariants = cva('bg-linear-to-br shadow-xs', {
|
||||
variants: {
|
||||
variant: {
|
||||
invitation: 'to-gray-50 from-teal-50',
|
||||
system: '',
|
||||
error: '',
|
||||
house: '',
|
||||
expired: '',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'invitation',
|
||||
},
|
||||
});
|
||||
|
||||
const NotificationItem = ({
|
||||
className,
|
||||
notify,
|
||||
...props
|
||||
}: NotifyProps & React.ComponentProps<'div'>) => {
|
||||
return (
|
||||
<Item
|
||||
variant="outline"
|
||||
className={cn(
|
||||
notifyVariants({
|
||||
variant: notify.type as VariantProps<
|
||||
typeof notifyVariants
|
||||
>['variant'],
|
||||
className,
|
||||
}),
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ItemHeader>
|
||||
{m.templates_title_notification({
|
||||
title: notify.title as Parameters<
|
||||
typeof m.templates_title_notification
|
||||
>[0]['title'],
|
||||
})}
|
||||
</ItemHeader>
|
||||
{notify.type === NOTIFICATION_TYPE.INVITATION && (
|
||||
<NotificationInvitation notify={notify} />
|
||||
)}
|
||||
</Item>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotificationItem;
|
||||
110
src/components/notification/notification-type/invitation.tsx
Normal file
110
src/components/notification/notification-type/invitation.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
ItemActions,
|
||||
ItemContent,
|
||||
ItemDescription,
|
||||
ItemTitle,
|
||||
} from '@/components/ui/item';
|
||||
import { m } from '@/paraglide/messages';
|
||||
import { acceptInvitation, rejectInvitation } from '@/service/house.api';
|
||||
import { notificationQueries } from '@/service/queries';
|
||||
import { formatTimeAgo } from '@/utils/helper';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
type NotifyProps = {
|
||||
notify: NotificationWithUser;
|
||||
};
|
||||
|
||||
const NotificationInvitation = ({ notify }: NotifyProps) => {
|
||||
const { house } = JSON.parse(notify.metadata || '');
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { mutate: acceptInvitationMutation } = useMutation({
|
||||
mutationFn: acceptInvitation,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [...notificationQueries.all, 'list'],
|
||||
});
|
||||
toast.success(m.notification_page_message_invitation_success(), {
|
||||
richColors: true,
|
||||
});
|
||||
},
|
||||
onError: (error: ReturnError) => {
|
||||
console.error(error);
|
||||
const code = error.code as Parameters<
|
||||
typeof m.backend_message
|
||||
>[0]['code'];
|
||||
toast.error(m.backend_message({ code }), {
|
||||
richColors: true,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const { mutate: rejectInvitationMutation } = useMutation({
|
||||
mutationFn: rejectInvitation,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [...notificationQueries.all, 'list'],
|
||||
});
|
||||
toast.success(m.notification_page_message_invitation_rejected(), {
|
||||
richColors: true,
|
||||
});
|
||||
},
|
||||
onError: (error: ReturnError) => {
|
||||
console.error(error);
|
||||
const code = error.code as Parameters<
|
||||
typeof m.backend_message
|
||||
>[0]['code'];
|
||||
toast.error(m.backend_message({ code }), {
|
||||
richColors: true,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const handleAgreeAction = async () => {
|
||||
if (notify.link) {
|
||||
acceptInvitationMutation({
|
||||
data: { id: notify.link, notificationId: notify.id },
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleRejectAction = () => {
|
||||
if (notify.link) {
|
||||
rejectInvitationMutation({
|
||||
data: { id: notify.link, notificationId: notify.id },
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ItemContent>
|
||||
<ItemTitle>
|
||||
{`${notify.user.name} (${notify.user.email})`}{' '}
|
||||
{m.templates_message_notification({
|
||||
message: notify.message as Parameters<
|
||||
typeof m.templates_message_notification
|
||||
>[0]['message'],
|
||||
name: house.name,
|
||||
})}
|
||||
</ItemTitle>
|
||||
<ItemDescription>{formatTimeAgo(notify.createdAt)}</ItemDescription>
|
||||
</ItemContent>
|
||||
{notify.link && (
|
||||
<ItemActions>
|
||||
<Button onClick={() => handleAgreeAction()}>
|
||||
{m.ui_agree_btn()}
|
||||
</Button>
|
||||
<Button variant="outline" onClick={() => handleRejectAction()}>
|
||||
{m.ui_reject_btn()}
|
||||
</Button>
|
||||
</ItemActions>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotificationInvitation;
|
||||
@@ -25,7 +25,7 @@ const AddNewUserButton = () => {
|
||||
return (
|
||||
<Dialog open={_open} onOpenChange={_setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button type="button" variant="default">
|
||||
<Button type="button" variant="default" className="cursor-pointer">
|
||||
<PlusIcon />
|
||||
{m.nav_add_new()}
|
||||
</Button>
|
||||
|
||||
@@ -62,3 +62,8 @@ export type Setting = Prisma.SettingModel
|
||||
*
|
||||
*/
|
||||
export type Audit = Prisma.AuditModel
|
||||
/**
|
||||
* Model Notification
|
||||
*
|
||||
*/
|
||||
export type Notification = Prisma.NotificationModel
|
||||
|
||||
@@ -84,3 +84,8 @@ export type Setting = Prisma.SettingModel
|
||||
*
|
||||
*/
|
||||
export type Audit = Prisma.AuditModel
|
||||
/**
|
||||
* Model Notification
|
||||
*
|
||||
*/
|
||||
export type Notification = Prisma.NotificationModel
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -392,7 +392,8 @@ export const ModelName = {
|
||||
Member: 'Member',
|
||||
Invitation: 'Invitation',
|
||||
Setting: 'Setting',
|
||||
Audit: 'Audit'
|
||||
Audit: 'Audit',
|
||||
Notification: 'Notification'
|
||||
} as const
|
||||
|
||||
export type ModelName = (typeof ModelName)[keyof typeof ModelName]
|
||||
@@ -408,7 +409,7 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
|
||||
omit: GlobalOmitOptions
|
||||
}
|
||||
meta: {
|
||||
modelProps: "user" | "session" | "account" | "verification" | "organization" | "member" | "invitation" | "setting" | "audit"
|
||||
modelProps: "user" | "session" | "account" | "verification" | "organization" | "member" | "invitation" | "setting" | "audit" | "notification"
|
||||
txIsolationLevel: TransactionIsolationLevel
|
||||
}
|
||||
model: {
|
||||
@@ -1078,6 +1079,80 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
|
||||
}
|
||||
}
|
||||
}
|
||||
Notification: {
|
||||
payload: Prisma.$NotificationPayload<ExtArgs>
|
||||
fields: Prisma.NotificationFieldRefs
|
||||
operations: {
|
||||
findUnique: {
|
||||
args: Prisma.NotificationFindUniqueArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$NotificationPayload> | null
|
||||
}
|
||||
findUniqueOrThrow: {
|
||||
args: Prisma.NotificationFindUniqueOrThrowArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$NotificationPayload>
|
||||
}
|
||||
findFirst: {
|
||||
args: Prisma.NotificationFindFirstArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$NotificationPayload> | null
|
||||
}
|
||||
findFirstOrThrow: {
|
||||
args: Prisma.NotificationFindFirstOrThrowArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$NotificationPayload>
|
||||
}
|
||||
findMany: {
|
||||
args: Prisma.NotificationFindManyArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$NotificationPayload>[]
|
||||
}
|
||||
create: {
|
||||
args: Prisma.NotificationCreateArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$NotificationPayload>
|
||||
}
|
||||
createMany: {
|
||||
args: Prisma.NotificationCreateManyArgs<ExtArgs>
|
||||
result: BatchPayload
|
||||
}
|
||||
createManyAndReturn: {
|
||||
args: Prisma.NotificationCreateManyAndReturnArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$NotificationPayload>[]
|
||||
}
|
||||
delete: {
|
||||
args: Prisma.NotificationDeleteArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$NotificationPayload>
|
||||
}
|
||||
update: {
|
||||
args: Prisma.NotificationUpdateArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$NotificationPayload>
|
||||
}
|
||||
deleteMany: {
|
||||
args: Prisma.NotificationDeleteManyArgs<ExtArgs>
|
||||
result: BatchPayload
|
||||
}
|
||||
updateMany: {
|
||||
args: Prisma.NotificationUpdateManyArgs<ExtArgs>
|
||||
result: BatchPayload
|
||||
}
|
||||
updateManyAndReturn: {
|
||||
args: Prisma.NotificationUpdateManyAndReturnArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$NotificationPayload>[]
|
||||
}
|
||||
upsert: {
|
||||
args: Prisma.NotificationUpsertArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$NotificationPayload>
|
||||
}
|
||||
aggregate: {
|
||||
args: Prisma.NotificationAggregateArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.Optional<Prisma.AggregateNotification>
|
||||
}
|
||||
groupBy: {
|
||||
args: Prisma.NotificationGroupByArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.Optional<Prisma.NotificationGroupByOutputType>[]
|
||||
}
|
||||
count: {
|
||||
args: Prisma.NotificationCountArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.Optional<Prisma.NotificationCountAggregateOutputType> | number
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} & {
|
||||
other: {
|
||||
@@ -1246,6 +1321,21 @@ export const AuditScalarFieldEnum = {
|
||||
export type AuditScalarFieldEnum = (typeof AuditScalarFieldEnum)[keyof typeof AuditScalarFieldEnum]
|
||||
|
||||
|
||||
export const NotificationScalarFieldEnum = {
|
||||
id: 'id',
|
||||
userId: 'userId',
|
||||
title: 'title',
|
||||
message: 'message',
|
||||
type: 'type',
|
||||
link: 'link',
|
||||
metadata: 'metadata',
|
||||
createdAt: 'createdAt',
|
||||
readAt: 'readAt'
|
||||
} as const
|
||||
|
||||
export type NotificationScalarFieldEnum = (typeof NotificationScalarFieldEnum)[keyof typeof NotificationScalarFieldEnum]
|
||||
|
||||
|
||||
export const SortOrder = {
|
||||
asc: 'asc',
|
||||
desc: 'desc'
|
||||
@@ -1428,6 +1518,7 @@ export type GlobalOmitConfig = {
|
||||
invitation?: Prisma.InvitationOmit
|
||||
setting?: Prisma.SettingOmit
|
||||
audit?: Prisma.AuditOmit
|
||||
notification?: Prisma.NotificationOmit
|
||||
}
|
||||
|
||||
/* Types for Logging */
|
||||
|
||||
@@ -59,7 +59,8 @@ export const ModelName = {
|
||||
Member: 'Member',
|
||||
Invitation: 'Invitation',
|
||||
Setting: 'Setting',
|
||||
Audit: 'Audit'
|
||||
Audit: 'Audit',
|
||||
Notification: 'Notification'
|
||||
} as const
|
||||
|
||||
export type ModelName = (typeof ModelName)[keyof typeof ModelName]
|
||||
@@ -207,6 +208,21 @@ export const AuditScalarFieldEnum = {
|
||||
export type AuditScalarFieldEnum = (typeof AuditScalarFieldEnum)[keyof typeof AuditScalarFieldEnum]
|
||||
|
||||
|
||||
export const NotificationScalarFieldEnum = {
|
||||
id: 'id',
|
||||
userId: 'userId',
|
||||
title: 'title',
|
||||
message: 'message',
|
||||
type: 'type',
|
||||
link: 'link',
|
||||
metadata: 'metadata',
|
||||
createdAt: 'createdAt',
|
||||
readAt: 'readAt'
|
||||
} as const
|
||||
|
||||
export type NotificationScalarFieldEnum = (typeof NotificationScalarFieldEnum)[keyof typeof NotificationScalarFieldEnum]
|
||||
|
||||
|
||||
export const SortOrder = {
|
||||
asc: 'asc',
|
||||
desc: 'desc'
|
||||
|
||||
@@ -17,4 +17,5 @@ export type * from './models/Member.ts'
|
||||
export type * from './models/Invitation.ts'
|
||||
export type * from './models/Setting.ts'
|
||||
export type * from './models/Audit.ts'
|
||||
export type * from './models/Notification.ts'
|
||||
export type * from './commonInputTypes.ts'
|
||||
1480
src/generated/prisma/models/Notification.ts
Normal file
1480
src/generated/prisma/models/Notification.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -233,6 +233,7 @@ export type UserWhereInput = {
|
||||
sessions?: Prisma.SessionListRelationFilter
|
||||
accounts?: Prisma.AccountListRelationFilter
|
||||
audit?: Prisma.AuditListRelationFilter
|
||||
notification?: Prisma.NotificationListRelationFilter
|
||||
members?: Prisma.MemberListRelationFilter
|
||||
invitations?: Prisma.InvitationListRelationFilter
|
||||
}
|
||||
@@ -252,6 +253,7 @@ export type UserOrderByWithRelationInput = {
|
||||
sessions?: Prisma.SessionOrderByRelationAggregateInput
|
||||
accounts?: Prisma.AccountOrderByRelationAggregateInput
|
||||
audit?: Prisma.AuditOrderByRelationAggregateInput
|
||||
notification?: Prisma.NotificationOrderByRelationAggregateInput
|
||||
members?: Prisma.MemberOrderByRelationAggregateInput
|
||||
invitations?: Prisma.InvitationOrderByRelationAggregateInput
|
||||
}
|
||||
@@ -274,6 +276,7 @@ export type UserWhereUniqueInput = Prisma.AtLeast<{
|
||||
sessions?: Prisma.SessionListRelationFilter
|
||||
accounts?: Prisma.AccountListRelationFilter
|
||||
audit?: Prisma.AuditListRelationFilter
|
||||
notification?: Prisma.NotificationListRelationFilter
|
||||
members?: Prisma.MemberListRelationFilter
|
||||
invitations?: Prisma.InvitationListRelationFilter
|
||||
}, "id" | "email">
|
||||
@@ -327,6 +330,7 @@ export type UserCreateInput = {
|
||||
sessions?: Prisma.SessionCreateNestedManyWithoutUserInput
|
||||
accounts?: Prisma.AccountCreateNestedManyWithoutUserInput
|
||||
audit?: Prisma.AuditCreateNestedManyWithoutUserInput
|
||||
notification?: Prisma.NotificationCreateNestedManyWithoutUserInput
|
||||
members?: Prisma.MemberCreateNestedManyWithoutUserInput
|
||||
invitations?: Prisma.InvitationCreateNestedManyWithoutUserInput
|
||||
}
|
||||
@@ -346,6 +350,7 @@ export type UserUncheckedCreateInput = {
|
||||
sessions?: Prisma.SessionUncheckedCreateNestedManyWithoutUserInput
|
||||
accounts?: Prisma.AccountUncheckedCreateNestedManyWithoutUserInput
|
||||
audit?: Prisma.AuditUncheckedCreateNestedManyWithoutUserInput
|
||||
notification?: Prisma.NotificationUncheckedCreateNestedManyWithoutUserInput
|
||||
members?: Prisma.MemberUncheckedCreateNestedManyWithoutUserInput
|
||||
invitations?: Prisma.InvitationUncheckedCreateNestedManyWithoutUserInput
|
||||
}
|
||||
@@ -365,6 +370,7 @@ export type UserUpdateInput = {
|
||||
sessions?: Prisma.SessionUpdateManyWithoutUserNestedInput
|
||||
accounts?: Prisma.AccountUpdateManyWithoutUserNestedInput
|
||||
audit?: Prisma.AuditUpdateManyWithoutUserNestedInput
|
||||
notification?: Prisma.NotificationUpdateManyWithoutUserNestedInput
|
||||
members?: Prisma.MemberUpdateManyWithoutUserNestedInput
|
||||
invitations?: Prisma.InvitationUpdateManyWithoutUserNestedInput
|
||||
}
|
||||
@@ -384,6 +390,7 @@ export type UserUncheckedUpdateInput = {
|
||||
sessions?: Prisma.SessionUncheckedUpdateManyWithoutUserNestedInput
|
||||
accounts?: Prisma.AccountUncheckedUpdateManyWithoutUserNestedInput
|
||||
audit?: Prisma.AuditUncheckedUpdateManyWithoutUserNestedInput
|
||||
notification?: Prisma.NotificationUncheckedUpdateManyWithoutUserNestedInput
|
||||
members?: Prisma.MemberUncheckedUpdateManyWithoutUserNestedInput
|
||||
invitations?: Prisma.InvitationUncheckedUpdateManyWithoutUserNestedInput
|
||||
}
|
||||
@@ -571,6 +578,20 @@ export type UserUpdateOneRequiredWithoutAuditNestedInput = {
|
||||
update?: Prisma.XOR<Prisma.XOR<Prisma.UserUpdateToOneWithWhereWithoutAuditInput, Prisma.UserUpdateWithoutAuditInput>, Prisma.UserUncheckedUpdateWithoutAuditInput>
|
||||
}
|
||||
|
||||
export type UserCreateNestedOneWithoutNotificationInput = {
|
||||
create?: Prisma.XOR<Prisma.UserCreateWithoutNotificationInput, Prisma.UserUncheckedCreateWithoutNotificationInput>
|
||||
connectOrCreate?: Prisma.UserCreateOrConnectWithoutNotificationInput
|
||||
connect?: Prisma.UserWhereUniqueInput
|
||||
}
|
||||
|
||||
export type UserUpdateOneRequiredWithoutNotificationNestedInput = {
|
||||
create?: Prisma.XOR<Prisma.UserCreateWithoutNotificationInput, Prisma.UserUncheckedCreateWithoutNotificationInput>
|
||||
connectOrCreate?: Prisma.UserCreateOrConnectWithoutNotificationInput
|
||||
upsert?: Prisma.UserUpsertWithoutNotificationInput
|
||||
connect?: Prisma.UserWhereUniqueInput
|
||||
update?: Prisma.XOR<Prisma.XOR<Prisma.UserUpdateToOneWithWhereWithoutNotificationInput, Prisma.UserUpdateWithoutNotificationInput>, Prisma.UserUncheckedUpdateWithoutNotificationInput>
|
||||
}
|
||||
|
||||
export type UserCreateWithoutSessionsInput = {
|
||||
id?: string
|
||||
name: string
|
||||
@@ -585,6 +606,7 @@ export type UserCreateWithoutSessionsInput = {
|
||||
banExpires?: Date | string | null
|
||||
accounts?: Prisma.AccountCreateNestedManyWithoutUserInput
|
||||
audit?: Prisma.AuditCreateNestedManyWithoutUserInput
|
||||
notification?: Prisma.NotificationCreateNestedManyWithoutUserInput
|
||||
members?: Prisma.MemberCreateNestedManyWithoutUserInput
|
||||
invitations?: Prisma.InvitationCreateNestedManyWithoutUserInput
|
||||
}
|
||||
@@ -603,6 +625,7 @@ export type UserUncheckedCreateWithoutSessionsInput = {
|
||||
banExpires?: Date | string | null
|
||||
accounts?: Prisma.AccountUncheckedCreateNestedManyWithoutUserInput
|
||||
audit?: Prisma.AuditUncheckedCreateNestedManyWithoutUserInput
|
||||
notification?: Prisma.NotificationUncheckedCreateNestedManyWithoutUserInput
|
||||
members?: Prisma.MemberUncheckedCreateNestedManyWithoutUserInput
|
||||
invitations?: Prisma.InvitationUncheckedCreateNestedManyWithoutUserInput
|
||||
}
|
||||
@@ -637,6 +660,7 @@ export type UserUpdateWithoutSessionsInput = {
|
||||
banExpires?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
accounts?: Prisma.AccountUpdateManyWithoutUserNestedInput
|
||||
audit?: Prisma.AuditUpdateManyWithoutUserNestedInput
|
||||
notification?: Prisma.NotificationUpdateManyWithoutUserNestedInput
|
||||
members?: Prisma.MemberUpdateManyWithoutUserNestedInput
|
||||
invitations?: Prisma.InvitationUpdateManyWithoutUserNestedInput
|
||||
}
|
||||
@@ -655,6 +679,7 @@ export type UserUncheckedUpdateWithoutSessionsInput = {
|
||||
banExpires?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
accounts?: Prisma.AccountUncheckedUpdateManyWithoutUserNestedInput
|
||||
audit?: Prisma.AuditUncheckedUpdateManyWithoutUserNestedInput
|
||||
notification?: Prisma.NotificationUncheckedUpdateManyWithoutUserNestedInput
|
||||
members?: Prisma.MemberUncheckedUpdateManyWithoutUserNestedInput
|
||||
invitations?: Prisma.InvitationUncheckedUpdateManyWithoutUserNestedInput
|
||||
}
|
||||
@@ -673,6 +698,7 @@ export type UserCreateWithoutAccountsInput = {
|
||||
banExpires?: Date | string | null
|
||||
sessions?: Prisma.SessionCreateNestedManyWithoutUserInput
|
||||
audit?: Prisma.AuditCreateNestedManyWithoutUserInput
|
||||
notification?: Prisma.NotificationCreateNestedManyWithoutUserInput
|
||||
members?: Prisma.MemberCreateNestedManyWithoutUserInput
|
||||
invitations?: Prisma.InvitationCreateNestedManyWithoutUserInput
|
||||
}
|
||||
@@ -691,6 +717,7 @@ export type UserUncheckedCreateWithoutAccountsInput = {
|
||||
banExpires?: Date | string | null
|
||||
sessions?: Prisma.SessionUncheckedCreateNestedManyWithoutUserInput
|
||||
audit?: Prisma.AuditUncheckedCreateNestedManyWithoutUserInput
|
||||
notification?: Prisma.NotificationUncheckedCreateNestedManyWithoutUserInput
|
||||
members?: Prisma.MemberUncheckedCreateNestedManyWithoutUserInput
|
||||
invitations?: Prisma.InvitationUncheckedCreateNestedManyWithoutUserInput
|
||||
}
|
||||
@@ -725,6 +752,7 @@ export type UserUpdateWithoutAccountsInput = {
|
||||
banExpires?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
sessions?: Prisma.SessionUpdateManyWithoutUserNestedInput
|
||||
audit?: Prisma.AuditUpdateManyWithoutUserNestedInput
|
||||
notification?: Prisma.NotificationUpdateManyWithoutUserNestedInput
|
||||
members?: Prisma.MemberUpdateManyWithoutUserNestedInput
|
||||
invitations?: Prisma.InvitationUpdateManyWithoutUserNestedInput
|
||||
}
|
||||
@@ -743,6 +771,7 @@ export type UserUncheckedUpdateWithoutAccountsInput = {
|
||||
banExpires?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
sessions?: Prisma.SessionUncheckedUpdateManyWithoutUserNestedInput
|
||||
audit?: Prisma.AuditUncheckedUpdateManyWithoutUserNestedInput
|
||||
notification?: Prisma.NotificationUncheckedUpdateManyWithoutUserNestedInput
|
||||
members?: Prisma.MemberUncheckedUpdateManyWithoutUserNestedInput
|
||||
invitations?: Prisma.InvitationUncheckedUpdateManyWithoutUserNestedInput
|
||||
}
|
||||
@@ -762,6 +791,7 @@ export type UserCreateWithoutMembersInput = {
|
||||
sessions?: Prisma.SessionCreateNestedManyWithoutUserInput
|
||||
accounts?: Prisma.AccountCreateNestedManyWithoutUserInput
|
||||
audit?: Prisma.AuditCreateNestedManyWithoutUserInput
|
||||
notification?: Prisma.NotificationCreateNestedManyWithoutUserInput
|
||||
invitations?: Prisma.InvitationCreateNestedManyWithoutUserInput
|
||||
}
|
||||
|
||||
@@ -780,6 +810,7 @@ export type UserUncheckedCreateWithoutMembersInput = {
|
||||
sessions?: Prisma.SessionUncheckedCreateNestedManyWithoutUserInput
|
||||
accounts?: Prisma.AccountUncheckedCreateNestedManyWithoutUserInput
|
||||
audit?: Prisma.AuditUncheckedCreateNestedManyWithoutUserInput
|
||||
notification?: Prisma.NotificationUncheckedCreateNestedManyWithoutUserInput
|
||||
invitations?: Prisma.InvitationUncheckedCreateNestedManyWithoutUserInput
|
||||
}
|
||||
|
||||
@@ -814,6 +845,7 @@ export type UserUpdateWithoutMembersInput = {
|
||||
sessions?: Prisma.SessionUpdateManyWithoutUserNestedInput
|
||||
accounts?: Prisma.AccountUpdateManyWithoutUserNestedInput
|
||||
audit?: Prisma.AuditUpdateManyWithoutUserNestedInput
|
||||
notification?: Prisma.NotificationUpdateManyWithoutUserNestedInput
|
||||
invitations?: Prisma.InvitationUpdateManyWithoutUserNestedInput
|
||||
}
|
||||
|
||||
@@ -832,6 +864,7 @@ export type UserUncheckedUpdateWithoutMembersInput = {
|
||||
sessions?: Prisma.SessionUncheckedUpdateManyWithoutUserNestedInput
|
||||
accounts?: Prisma.AccountUncheckedUpdateManyWithoutUserNestedInput
|
||||
audit?: Prisma.AuditUncheckedUpdateManyWithoutUserNestedInput
|
||||
notification?: Prisma.NotificationUncheckedUpdateManyWithoutUserNestedInput
|
||||
invitations?: Prisma.InvitationUncheckedUpdateManyWithoutUserNestedInput
|
||||
}
|
||||
|
||||
@@ -850,6 +883,7 @@ export type UserCreateWithoutInvitationsInput = {
|
||||
sessions?: Prisma.SessionCreateNestedManyWithoutUserInput
|
||||
accounts?: Prisma.AccountCreateNestedManyWithoutUserInput
|
||||
audit?: Prisma.AuditCreateNestedManyWithoutUserInput
|
||||
notification?: Prisma.NotificationCreateNestedManyWithoutUserInput
|
||||
members?: Prisma.MemberCreateNestedManyWithoutUserInput
|
||||
}
|
||||
|
||||
@@ -868,6 +902,7 @@ export type UserUncheckedCreateWithoutInvitationsInput = {
|
||||
sessions?: Prisma.SessionUncheckedCreateNestedManyWithoutUserInput
|
||||
accounts?: Prisma.AccountUncheckedCreateNestedManyWithoutUserInput
|
||||
audit?: Prisma.AuditUncheckedCreateNestedManyWithoutUserInput
|
||||
notification?: Prisma.NotificationUncheckedCreateNestedManyWithoutUserInput
|
||||
members?: Prisma.MemberUncheckedCreateNestedManyWithoutUserInput
|
||||
}
|
||||
|
||||
@@ -902,6 +937,7 @@ export type UserUpdateWithoutInvitationsInput = {
|
||||
sessions?: Prisma.SessionUpdateManyWithoutUserNestedInput
|
||||
accounts?: Prisma.AccountUpdateManyWithoutUserNestedInput
|
||||
audit?: Prisma.AuditUpdateManyWithoutUserNestedInput
|
||||
notification?: Prisma.NotificationUpdateManyWithoutUserNestedInput
|
||||
members?: Prisma.MemberUpdateManyWithoutUserNestedInput
|
||||
}
|
||||
|
||||
@@ -920,6 +956,7 @@ export type UserUncheckedUpdateWithoutInvitationsInput = {
|
||||
sessions?: Prisma.SessionUncheckedUpdateManyWithoutUserNestedInput
|
||||
accounts?: Prisma.AccountUncheckedUpdateManyWithoutUserNestedInput
|
||||
audit?: Prisma.AuditUncheckedUpdateManyWithoutUserNestedInput
|
||||
notification?: Prisma.NotificationUncheckedUpdateManyWithoutUserNestedInput
|
||||
members?: Prisma.MemberUncheckedUpdateManyWithoutUserNestedInput
|
||||
}
|
||||
|
||||
@@ -937,6 +974,7 @@ export type UserCreateWithoutAuditInput = {
|
||||
banExpires?: Date | string | null
|
||||
sessions?: Prisma.SessionCreateNestedManyWithoutUserInput
|
||||
accounts?: Prisma.AccountCreateNestedManyWithoutUserInput
|
||||
notification?: Prisma.NotificationCreateNestedManyWithoutUserInput
|
||||
members?: Prisma.MemberCreateNestedManyWithoutUserInput
|
||||
invitations?: Prisma.InvitationCreateNestedManyWithoutUserInput
|
||||
}
|
||||
@@ -955,6 +993,7 @@ export type UserUncheckedCreateWithoutAuditInput = {
|
||||
banExpires?: Date | string | null
|
||||
sessions?: Prisma.SessionUncheckedCreateNestedManyWithoutUserInput
|
||||
accounts?: Prisma.AccountUncheckedCreateNestedManyWithoutUserInput
|
||||
notification?: Prisma.NotificationUncheckedCreateNestedManyWithoutUserInput
|
||||
members?: Prisma.MemberUncheckedCreateNestedManyWithoutUserInput
|
||||
invitations?: Prisma.InvitationUncheckedCreateNestedManyWithoutUserInput
|
||||
}
|
||||
@@ -989,6 +1028,7 @@ export type UserUpdateWithoutAuditInput = {
|
||||
banExpires?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
sessions?: Prisma.SessionUpdateManyWithoutUserNestedInput
|
||||
accounts?: Prisma.AccountUpdateManyWithoutUserNestedInput
|
||||
notification?: Prisma.NotificationUpdateManyWithoutUserNestedInput
|
||||
members?: Prisma.MemberUpdateManyWithoutUserNestedInput
|
||||
invitations?: Prisma.InvitationUpdateManyWithoutUserNestedInput
|
||||
}
|
||||
@@ -1007,6 +1047,99 @@ export type UserUncheckedUpdateWithoutAuditInput = {
|
||||
banExpires?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
sessions?: Prisma.SessionUncheckedUpdateManyWithoutUserNestedInput
|
||||
accounts?: Prisma.AccountUncheckedUpdateManyWithoutUserNestedInput
|
||||
notification?: Prisma.NotificationUncheckedUpdateManyWithoutUserNestedInput
|
||||
members?: Prisma.MemberUncheckedUpdateManyWithoutUserNestedInput
|
||||
invitations?: Prisma.InvitationUncheckedUpdateManyWithoutUserNestedInput
|
||||
}
|
||||
|
||||
export type UserCreateWithoutNotificationInput = {
|
||||
id?: string
|
||||
name: string
|
||||
email: string
|
||||
emailVerified?: boolean
|
||||
image?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
role?: string | null
|
||||
banned?: boolean | null
|
||||
banReason?: string | null
|
||||
banExpires?: Date | string | null
|
||||
sessions?: Prisma.SessionCreateNestedManyWithoutUserInput
|
||||
accounts?: Prisma.AccountCreateNestedManyWithoutUserInput
|
||||
audit?: Prisma.AuditCreateNestedManyWithoutUserInput
|
||||
members?: Prisma.MemberCreateNestedManyWithoutUserInput
|
||||
invitations?: Prisma.InvitationCreateNestedManyWithoutUserInput
|
||||
}
|
||||
|
||||
export type UserUncheckedCreateWithoutNotificationInput = {
|
||||
id?: string
|
||||
name: string
|
||||
email: string
|
||||
emailVerified?: boolean
|
||||
image?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
role?: string | null
|
||||
banned?: boolean | null
|
||||
banReason?: string | null
|
||||
banExpires?: Date | string | null
|
||||
sessions?: Prisma.SessionUncheckedCreateNestedManyWithoutUserInput
|
||||
accounts?: Prisma.AccountUncheckedCreateNestedManyWithoutUserInput
|
||||
audit?: Prisma.AuditUncheckedCreateNestedManyWithoutUserInput
|
||||
members?: Prisma.MemberUncheckedCreateNestedManyWithoutUserInput
|
||||
invitations?: Prisma.InvitationUncheckedCreateNestedManyWithoutUserInput
|
||||
}
|
||||
|
||||
export type UserCreateOrConnectWithoutNotificationInput = {
|
||||
where: Prisma.UserWhereUniqueInput
|
||||
create: Prisma.XOR<Prisma.UserCreateWithoutNotificationInput, Prisma.UserUncheckedCreateWithoutNotificationInput>
|
||||
}
|
||||
|
||||
export type UserUpsertWithoutNotificationInput = {
|
||||
update: Prisma.XOR<Prisma.UserUpdateWithoutNotificationInput, Prisma.UserUncheckedUpdateWithoutNotificationInput>
|
||||
create: Prisma.XOR<Prisma.UserCreateWithoutNotificationInput, Prisma.UserUncheckedCreateWithoutNotificationInput>
|
||||
where?: Prisma.UserWhereInput
|
||||
}
|
||||
|
||||
export type UserUpdateToOneWithWhereWithoutNotificationInput = {
|
||||
where?: Prisma.UserWhereInput
|
||||
data: Prisma.XOR<Prisma.UserUpdateWithoutNotificationInput, Prisma.UserUncheckedUpdateWithoutNotificationInput>
|
||||
}
|
||||
|
||||
export type UserUpdateWithoutNotificationInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
email?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
emailVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
role?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
banned?: Prisma.NullableBoolFieldUpdateOperationsInput | boolean | null
|
||||
banReason?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
banExpires?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
sessions?: Prisma.SessionUpdateManyWithoutUserNestedInput
|
||||
accounts?: Prisma.AccountUpdateManyWithoutUserNestedInput
|
||||
audit?: Prisma.AuditUpdateManyWithoutUserNestedInput
|
||||
members?: Prisma.MemberUpdateManyWithoutUserNestedInput
|
||||
invitations?: Prisma.InvitationUpdateManyWithoutUserNestedInput
|
||||
}
|
||||
|
||||
export type UserUncheckedUpdateWithoutNotificationInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
email?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
emailVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
role?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
banned?: Prisma.NullableBoolFieldUpdateOperationsInput | boolean | null
|
||||
banReason?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
banExpires?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
sessions?: Prisma.SessionUncheckedUpdateManyWithoutUserNestedInput
|
||||
accounts?: Prisma.AccountUncheckedUpdateManyWithoutUserNestedInput
|
||||
audit?: Prisma.AuditUncheckedUpdateManyWithoutUserNestedInput
|
||||
members?: Prisma.MemberUncheckedUpdateManyWithoutUserNestedInput
|
||||
invitations?: Prisma.InvitationUncheckedUpdateManyWithoutUserNestedInput
|
||||
}
|
||||
@@ -1020,6 +1153,7 @@ export type UserCountOutputType = {
|
||||
sessions: number
|
||||
accounts: number
|
||||
audit: number
|
||||
notification: number
|
||||
members: number
|
||||
invitations: number
|
||||
}
|
||||
@@ -1028,6 +1162,7 @@ export type UserCountOutputTypeSelect<ExtArgs extends runtime.Types.Extensions.I
|
||||
sessions?: boolean | UserCountOutputTypeCountSessionsArgs
|
||||
accounts?: boolean | UserCountOutputTypeCountAccountsArgs
|
||||
audit?: boolean | UserCountOutputTypeCountAuditArgs
|
||||
notification?: boolean | UserCountOutputTypeCountNotificationArgs
|
||||
members?: boolean | UserCountOutputTypeCountMembersArgs
|
||||
invitations?: boolean | UserCountOutputTypeCountInvitationsArgs
|
||||
}
|
||||
@@ -1063,6 +1198,13 @@ export type UserCountOutputTypeCountAuditArgs<ExtArgs extends runtime.Types.Exte
|
||||
where?: Prisma.AuditWhereInput
|
||||
}
|
||||
|
||||
/**
|
||||
* UserCountOutputType without action
|
||||
*/
|
||||
export type UserCountOutputTypeCountNotificationArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
where?: Prisma.NotificationWhereInput
|
||||
}
|
||||
|
||||
/**
|
||||
* UserCountOutputType without action
|
||||
*/
|
||||
@@ -1093,6 +1235,7 @@ export type UserSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = r
|
||||
sessions?: boolean | Prisma.User$sessionsArgs<ExtArgs>
|
||||
accounts?: boolean | Prisma.User$accountsArgs<ExtArgs>
|
||||
audit?: boolean | Prisma.User$auditArgs<ExtArgs>
|
||||
notification?: boolean | Prisma.User$notificationArgs<ExtArgs>
|
||||
members?: boolean | Prisma.User$membersArgs<ExtArgs>
|
||||
invitations?: boolean | Prisma.User$invitationsArgs<ExtArgs>
|
||||
_count?: boolean | Prisma.UserCountOutputTypeDefaultArgs<ExtArgs>
|
||||
@@ -1145,6 +1288,7 @@ export type UserInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs =
|
||||
sessions?: boolean | Prisma.User$sessionsArgs<ExtArgs>
|
||||
accounts?: boolean | Prisma.User$accountsArgs<ExtArgs>
|
||||
audit?: boolean | Prisma.User$auditArgs<ExtArgs>
|
||||
notification?: boolean | Prisma.User$notificationArgs<ExtArgs>
|
||||
members?: boolean | Prisma.User$membersArgs<ExtArgs>
|
||||
invitations?: boolean | Prisma.User$invitationsArgs<ExtArgs>
|
||||
_count?: boolean | Prisma.UserCountOutputTypeDefaultArgs<ExtArgs>
|
||||
@@ -1158,6 +1302,7 @@ export type $UserPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs =
|
||||
sessions: Prisma.$SessionPayload<ExtArgs>[]
|
||||
accounts: Prisma.$AccountPayload<ExtArgs>[]
|
||||
audit: Prisma.$AuditPayload<ExtArgs>[]
|
||||
notification: Prisma.$NotificationPayload<ExtArgs>[]
|
||||
members: Prisma.$MemberPayload<ExtArgs>[]
|
||||
invitations: Prisma.$InvitationPayload<ExtArgs>[]
|
||||
}
|
||||
@@ -1570,6 +1715,7 @@ export interface Prisma__UserClient<T, Null = never, ExtArgs extends runtime.Typ
|
||||
sessions<T extends Prisma.User$sessionsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$sessionsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$SessionPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||
accounts<T extends Prisma.User$accountsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$accountsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$AccountPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||
audit<T extends Prisma.User$auditArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$auditArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$AuditPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||
notification<T extends Prisma.User$notificationArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$notificationArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$NotificationPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||
members<T extends Prisma.User$membersArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$membersArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$MemberPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||
invitations<T extends Prisma.User$invitationsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$invitationsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$InvitationPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||
/**
|
||||
@@ -2071,6 +2217,30 @@ export type User$auditArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs
|
||||
distinct?: Prisma.AuditScalarFieldEnum | Prisma.AuditScalarFieldEnum[]
|
||||
}
|
||||
|
||||
/**
|
||||
* User.notification
|
||||
*/
|
||||
export type User$notificationArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
/**
|
||||
* Select specific fields to fetch from the Notification
|
||||
*/
|
||||
select?: Prisma.NotificationSelect<ExtArgs> | null
|
||||
/**
|
||||
* Omit specific fields from the Notification
|
||||
*/
|
||||
omit?: Prisma.NotificationOmit<ExtArgs> | null
|
||||
/**
|
||||
* Choose, which related nodes to fetch as well
|
||||
*/
|
||||
include?: Prisma.NotificationInclude<ExtArgs> | null
|
||||
where?: Prisma.NotificationWhereInput
|
||||
orderBy?: Prisma.NotificationOrderByWithRelationInput | Prisma.NotificationOrderByWithRelationInput[]
|
||||
cursor?: Prisma.NotificationWhereUniqueInput
|
||||
take?: number
|
||||
skip?: number
|
||||
distinct?: Prisma.NotificationScalarFieldEnum | Prisma.NotificationScalarFieldEnum[]
|
||||
}
|
||||
|
||||
/**
|
||||
* User.members
|
||||
*/
|
||||
|
||||
@@ -20,6 +20,7 @@ import { Route as appauthAccountRouteRouteImport } from './routes/(app)/(auth)/a
|
||||
import { Route as appauthManagementIndexRouteImport } from './routes/(app)/(auth)/management/index'
|
||||
import { Route as appauthKanriIndexRouteImport } from './routes/(app)/(auth)/kanri/index'
|
||||
import { Route as appauthAccountIndexRouteImport } from './routes/(app)/(auth)/account/index'
|
||||
import { Route as appauthManagementNotificationsRouteImport } from './routes/(app)/(auth)/management/notifications'
|
||||
import { Route as appauthManagementHousesRouteImport } from './routes/(app)/(auth)/management/houses'
|
||||
import { Route as appauthManagementDashboardRouteImport } from './routes/(app)/(auth)/management/dashboard'
|
||||
import { Route as appauthKanriUsersRouteImport } from './routes/(app)/(auth)/kanri/users'
|
||||
@@ -83,6 +84,12 @@ const appauthAccountIndexRoute = appauthAccountIndexRouteImport.update({
|
||||
path: '/',
|
||||
getParentRoute: () => appauthAccountRouteRoute,
|
||||
} as any)
|
||||
const appauthManagementNotificationsRoute =
|
||||
appauthManagementNotificationsRouteImport.update({
|
||||
id: '/notifications',
|
||||
path: '/notifications',
|
||||
getParentRoute: () => appauthManagementRouteRoute,
|
||||
} as any)
|
||||
const appauthManagementHousesRoute = appauthManagementHousesRouteImport.update({
|
||||
id: '/houses',
|
||||
path: '/houses',
|
||||
@@ -147,6 +154,7 @@ export interface FileRoutesByFullPath {
|
||||
'/kanri/users': typeof appauthKanriUsersRoute
|
||||
'/management/dashboard': typeof appauthManagementDashboardRoute
|
||||
'/management/houses': typeof appauthManagementHousesRoute
|
||||
'/management/notifications': typeof appauthManagementNotificationsRoute
|
||||
'/account/': typeof appauthAccountIndexRoute
|
||||
'/kanri/': typeof appauthKanriIndexRoute
|
||||
'/management/': typeof appauthManagementIndexRoute
|
||||
@@ -164,6 +172,7 @@ export interface FileRoutesByTo {
|
||||
'/kanri/users': typeof appauthKanriUsersRoute
|
||||
'/management/dashboard': typeof appauthManagementDashboardRoute
|
||||
'/management/houses': typeof appauthManagementHousesRoute
|
||||
'/management/notifications': typeof appauthManagementNotificationsRoute
|
||||
'/account': typeof appauthAccountIndexRoute
|
||||
'/kanri': typeof appauthKanriIndexRoute
|
||||
'/management': typeof appauthManagementIndexRoute
|
||||
@@ -187,6 +196,7 @@ export interface FileRoutesById {
|
||||
'/(app)/(auth)/kanri/users': typeof appauthKanriUsersRoute
|
||||
'/(app)/(auth)/management/dashboard': typeof appauthManagementDashboardRoute
|
||||
'/(app)/(auth)/management/houses': typeof appauthManagementHousesRoute
|
||||
'/(app)/(auth)/management/notifications': typeof appauthManagementNotificationsRoute
|
||||
'/(app)/(auth)/account/': typeof appauthAccountIndexRoute
|
||||
'/(app)/(auth)/kanri/': typeof appauthKanriIndexRoute
|
||||
'/(app)/(auth)/management/': typeof appauthManagementIndexRoute
|
||||
@@ -209,6 +219,7 @@ export interface FileRouteTypes {
|
||||
| '/kanri/users'
|
||||
| '/management/dashboard'
|
||||
| '/management/houses'
|
||||
| '/management/notifications'
|
||||
| '/account/'
|
||||
| '/kanri/'
|
||||
| '/management/'
|
||||
@@ -226,6 +237,7 @@ export interface FileRouteTypes {
|
||||
| '/kanri/users'
|
||||
| '/management/dashboard'
|
||||
| '/management/houses'
|
||||
| '/management/notifications'
|
||||
| '/account'
|
||||
| '/kanri'
|
||||
| '/management'
|
||||
@@ -248,6 +260,7 @@ export interface FileRouteTypes {
|
||||
| '/(app)/(auth)/kanri/users'
|
||||
| '/(app)/(auth)/management/dashboard'
|
||||
| '/(app)/(auth)/management/houses'
|
||||
| '/(app)/(auth)/management/notifications'
|
||||
| '/(app)/(auth)/account/'
|
||||
| '/(app)/(auth)/kanri/'
|
||||
| '/(app)/(auth)/management/'
|
||||
@@ -338,6 +351,13 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof appauthAccountIndexRouteImport
|
||||
parentRoute: typeof appauthAccountRouteRoute
|
||||
}
|
||||
'/(app)/(auth)/management/notifications': {
|
||||
id: '/(app)/(auth)/management/notifications'
|
||||
path: '/notifications'
|
||||
fullPath: '/management/notifications'
|
||||
preLoaderRoute: typeof appauthManagementNotificationsRouteImport
|
||||
parentRoute: typeof appauthManagementRouteRoute
|
||||
}
|
||||
'/(app)/(auth)/management/houses': {
|
||||
id: '/(app)/(auth)/management/houses'
|
||||
path: '/houses'
|
||||
@@ -443,6 +463,7 @@ const appauthKanriRouteRouteWithChildren =
|
||||
interface appauthManagementRouteRouteChildren {
|
||||
appauthManagementDashboardRoute: typeof appauthManagementDashboardRoute
|
||||
appauthManagementHousesRoute: typeof appauthManagementHousesRoute
|
||||
appauthManagementNotificationsRoute: typeof appauthManagementNotificationsRoute
|
||||
appauthManagementIndexRoute: typeof appauthManagementIndexRoute
|
||||
}
|
||||
|
||||
@@ -450,6 +471,7 @@ const appauthManagementRouteRouteChildren: appauthManagementRouteRouteChildren =
|
||||
{
|
||||
appauthManagementDashboardRoute: appauthManagementDashboardRoute,
|
||||
appauthManagementHousesRoute: appauthManagementHousesRoute,
|
||||
appauthManagementNotificationsRoute: appauthManagementNotificationsRoute,
|
||||
appauthManagementIndexRoute: appauthManagementIndexRoute,
|
||||
}
|
||||
|
||||
|
||||
59
src/routes/(app)/(auth)/management/notifications.tsx
Normal file
59
src/routes/(app)/(auth)/management/notifications.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import Pagination from '@/components/Pagination';
|
||||
import { m } from '@/paraglide/messages';
|
||||
import { notificationQueries } from '@/service/queries';
|
||||
import NotificationItem from '@components/notification/notification-item';
|
||||
import { BellRingingIcon } from '@phosphor-icons/react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
import { Card, CardHeader, CardTitle } from '@ui/card';
|
||||
import { useState } from 'react';
|
||||
|
||||
export const Route = createFileRoute('/(app)/(auth)/management/notifications')({
|
||||
component: RouteComponent,
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
const [page, setPage] = useState(1);
|
||||
const { data, isLoading } = useQuery(
|
||||
notificationQueries.list({
|
||||
page,
|
||||
limit: 10,
|
||||
}),
|
||||
);
|
||||
|
||||
if (isLoading) return null;
|
||||
|
||||
return (
|
||||
<div className="@container/main p-4">
|
||||
<div className="*:data-[slot=card]:from-primary/5 *:data-[slot=card]:to-card dark:*:data-[slot=card]:bg-card *:data-[slot=card]:bg-linear-to-br *:data-[slot=card]:shadow-xs">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xl flex items-center gap-2">
|
||||
<BellRingingIcon
|
||||
size={24}
|
||||
className="origin-top animate-bell-ring"
|
||||
/>
|
||||
{m.ui_label_notifications()}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
{data && data.result.length > 0 ? (
|
||||
<div className="mt-5 max-w-200 min-w-107 mx-auto flex flex-col gap-5">
|
||||
{data.result.map((notify) => {
|
||||
return <NotificationItem notify={notify} key={notify.id} />;
|
||||
})}
|
||||
<Pagination
|
||||
currentPage={page}
|
||||
totalPages={data?.pagination.totalPage}
|
||||
onPageChange={setPage}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-5 max-w-full h-50 mx-auto flex justify-center items-center">
|
||||
{m.common_no_notify()}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -34,23 +34,24 @@ export const getAllAudit = createServerFn({ method: 'GET' })
|
||||
},
|
||||
],
|
||||
};
|
||||
const [auditlog, total]: [AuditWithUser[], number] = await Promise.all([
|
||||
await prisma.audit.findMany({
|
||||
where,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
const [auditlog, total]: [AuditWithUser[], number] =
|
||||
await prisma.$transaction([
|
||||
prisma.audit.findMany({
|
||||
where,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
take: data.limit,
|
||||
skip,
|
||||
}),
|
||||
await prisma.audit.count({ where }),
|
||||
]);
|
||||
take: data.limit,
|
||||
skip,
|
||||
}),
|
||||
prisma.audit.count({ where }),
|
||||
]);
|
||||
|
||||
const totalPage = Math.ceil(+total / data.limit);
|
||||
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import { prisma } from '@/db';
|
||||
import { OrganizationWhereInput } from '@/generated/prisma/models';
|
||||
import { DB_TABLE, LOG_ACTION } from '@/types/enum';
|
||||
import { DB_TABLE, LOG_ACTION, NOTIFICATION_TYPE } from '@/types/enum';
|
||||
import { auth } from '@lib/auth';
|
||||
import { parseError } from '@lib/errors';
|
||||
import { authMiddleware } from '@lib/middleware';
|
||||
import { createServerFn } from '@tanstack/react-start';
|
||||
import { getRequestHeaders } from '@tanstack/react-start/server';
|
||||
import {
|
||||
actionInvitationSchema,
|
||||
baseHouse,
|
||||
houseCreateBESchema,
|
||||
houseEditBESchema,
|
||||
houseListSchema,
|
||||
invitationCreateBESchema,
|
||||
} from './house.schema';
|
||||
import { createAuditLog } from './repository';
|
||||
import { createAuditLog, createNotification } from './repository';
|
||||
|
||||
export const getAllHouse = createServerFn({ method: 'GET' })
|
||||
.middleware([authMiddleware])
|
||||
@@ -266,12 +267,31 @@ export const invitationMember = createServerFn({ method: 'POST' })
|
||||
organizationId: data.houseId,
|
||||
};
|
||||
|
||||
const cuser = await prisma.user.findUnique({
|
||||
where: { email: data.email },
|
||||
});
|
||||
const chouse = await prisma.organization.findUnique({
|
||||
where: { id: data.houseId },
|
||||
});
|
||||
|
||||
const result = await auth.api.createInvitation({
|
||||
body,
|
||||
headers,
|
||||
});
|
||||
|
||||
if (result) {
|
||||
if (result && cuser && chouse) {
|
||||
await createNotification({
|
||||
type: NOTIFICATION_TYPE.INVITATION,
|
||||
userId: cuser.id,
|
||||
title: 'INVITATION_HOUSE',
|
||||
message: 'INVITATION_HOUSE',
|
||||
link: result.id,
|
||||
metadata: JSON.stringify({
|
||||
house: chouse,
|
||||
}),
|
||||
readAt: null,
|
||||
});
|
||||
|
||||
await createAuditLog({
|
||||
action: LOG_ACTION.CREATE,
|
||||
tableName: DB_TABLE.INVITATION,
|
||||
@@ -302,6 +322,81 @@ export const cancelInvitation = createServerFn({ method: 'POST' })
|
||||
},
|
||||
headers,
|
||||
});
|
||||
|
||||
if (result) {
|
||||
const notification = await prisma.notification.findFirst({
|
||||
where: { link: data.id },
|
||||
});
|
||||
|
||||
if (notification) {
|
||||
await prisma.notification.update({
|
||||
where: { id: notification.id },
|
||||
data: { link: null },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
const { message, code } = parseError(error);
|
||||
throw { message, code };
|
||||
}
|
||||
});
|
||||
|
||||
export const acceptInvitation = createServerFn({ method: 'POST' })
|
||||
.middleware([authMiddleware])
|
||||
.inputValidator(actionInvitationSchema)
|
||||
.handler(async ({ data }) => {
|
||||
try {
|
||||
const result = await prisma.invitation.update({
|
||||
where: { id: data.id },
|
||||
data: { status: 'accepted' },
|
||||
});
|
||||
|
||||
if (result) {
|
||||
const notify = await prisma.notification.update({
|
||||
where: { id: data.notificationId },
|
||||
data: { link: null },
|
||||
});
|
||||
|
||||
await auth.api.addMember({
|
||||
body: {
|
||||
userId: notify.userId,
|
||||
organizationId: result.organizationId,
|
||||
role: (result.role as 'admin' | 'owner' | 'member') || 'member',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
const { message, code } = parseError(error);
|
||||
throw { message, code };
|
||||
}
|
||||
});
|
||||
|
||||
export const rejectInvitation = createServerFn({ method: 'POST' })
|
||||
.middleware([authMiddleware])
|
||||
.inputValidator(actionInvitationSchema)
|
||||
.handler(async ({ data }) => {
|
||||
try {
|
||||
const headers = getRequestHeaders();
|
||||
const result = await auth.api.rejectInvitation({
|
||||
body: {
|
||||
invitationId: data.id,
|
||||
},
|
||||
headers,
|
||||
});
|
||||
|
||||
if (result) {
|
||||
await prisma.notification.update({
|
||||
where: { id: data.notificationId },
|
||||
data: { link: null },
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
@@ -49,6 +49,10 @@ export const RoleHouseEnum = z.enum(
|
||||
m.users_page_message_role_select(),
|
||||
);
|
||||
|
||||
const baseInvitation = z.object({
|
||||
id: z.string().nonempty(m.invitation_not_found()),
|
||||
});
|
||||
|
||||
const invitationCreateSchema = z.object({
|
||||
email: z
|
||||
.string()
|
||||
@@ -63,3 +67,7 @@ export const invitationCreateFESchema = invitationCreateSchema.extend({
|
||||
export const invitationCreateBESchema = invitationCreateSchema.extend({
|
||||
role: RoleHouseEnum,
|
||||
});
|
||||
|
||||
export const actionInvitationSchema = baseInvitation.extend({
|
||||
notificationId: z.string().nonempty(m.notification_page_notify_not_found()),
|
||||
});
|
||||
|
||||
80
src/service/notify.api.ts
Normal file
80
src/service/notify.api.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { prisma } from '@/db';
|
||||
import { parseError } from '@lib/errors';
|
||||
import { authMiddleware } from '@lib/middleware';
|
||||
import { createServerFn } from '@tanstack/react-start';
|
||||
import { notificationListSchema } from './notify.schema';
|
||||
|
||||
export const getTopFiveNotification = createServerFn({ method: 'GET' })
|
||||
.middleware([authMiddleware])
|
||||
.handler(async ({ context: { user } }) => {
|
||||
try {
|
||||
const list = await prisma.notification.findMany({
|
||||
where: {
|
||||
userId: user.id,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'asc',
|
||||
},
|
||||
take: 5,
|
||||
});
|
||||
|
||||
return list;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
const { message, code } = parseError(error);
|
||||
throw { message, code };
|
||||
}
|
||||
});
|
||||
|
||||
export const getAllNotifications = createServerFn({ method: 'GET' })
|
||||
.middleware([authMiddleware])
|
||||
.inputValidator(notificationListSchema)
|
||||
.handler(async ({ data, context: { user } }) => {
|
||||
try {
|
||||
const skip = (data.page - 1) * data.limit;
|
||||
|
||||
const [list, total]: [NotificationWithUser[], number] =
|
||||
await prisma.$transaction([
|
||||
prisma.notification.findMany({
|
||||
where: {
|
||||
userId: user.id,
|
||||
},
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
image: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'asc',
|
||||
},
|
||||
take: data.limit,
|
||||
skip,
|
||||
}),
|
||||
prisma.notification.count({
|
||||
where: {
|
||||
userId: user.id,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
const totalPage = Math.ceil(+total / data.limit);
|
||||
|
||||
return {
|
||||
result: list,
|
||||
pagination: {
|
||||
currentPage: data.page,
|
||||
totalPage,
|
||||
totalItem: total,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
const { message, code } = parseError(error);
|
||||
throw { message, code };
|
||||
}
|
||||
});
|
||||
6
src/service/notify.schema.ts
Normal file
6
src/service/notify.schema.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import z from 'zod';
|
||||
|
||||
export const notificationListSchema = z.object({
|
||||
page: z.coerce.number().min(1).default(1),
|
||||
limit: z.coerce.number().min(10).max(100).default(10),
|
||||
});
|
||||
@@ -2,6 +2,7 @@ import { getSession } from '@lib/auth/session';
|
||||
import { queryOptions } from '@tanstack/react-query';
|
||||
import { getAllAudit } from './audit.api';
|
||||
import { getAllHouse, getCurrentUserHouses } from './house.api';
|
||||
import { getAllNotifications, getTopFiveNotification } from './notify.api';
|
||||
import {
|
||||
getAdminSettings,
|
||||
getCurrentUserLanguage,
|
||||
@@ -75,3 +76,17 @@ export const housesQueries = {
|
||||
queryFn: () => getCurrentUserHouses(),
|
||||
}),
|
||||
};
|
||||
|
||||
export const notificationQueries = {
|
||||
all: ['notification'],
|
||||
list: (params: { page: number; limit: number }) =>
|
||||
queryOptions({
|
||||
queryKey: [...notificationQueries.all, 'list', params],
|
||||
queryFn: () => getAllNotifications({ data: params }),
|
||||
}),
|
||||
topFive: () =>
|
||||
queryOptions({
|
||||
queryKey: [...notificationQueries.all, 'topFive'],
|
||||
queryFn: () => getTopFiveNotification(),
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { prisma } from '@/db';
|
||||
import { Audit, Setting } from '@/generated/prisma/client';
|
||||
import { Audit, Notification, Setting } from '@/generated/prisma/client';
|
||||
|
||||
type AdminSettingValue = Pick<Setting, 'id' | 'key' | 'value'>;
|
||||
|
||||
@@ -61,3 +61,13 @@ export const getInitialOrganization = async (userId: string) => {
|
||||
|
||||
return organization;
|
||||
};
|
||||
|
||||
export const createNotification = async (
|
||||
data: Omit<Notification, 'id' | 'createdAt'>,
|
||||
) => {
|
||||
await prisma.notification.create({
|
||||
data: {
|
||||
...data,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -5,6 +5,22 @@
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@theme {
|
||||
--animate-bell-ring: bell-ring 4s .7s ease-in-out infinite;
|
||||
|
||||
@keyframes bell-ring {
|
||||
0% { transform: rotate(0deg); }
|
||||
10% { transform: rotate(15deg); }
|
||||
20% { transform: rotate(-15deg); }
|
||||
30% { transform: rotate(10deg); }
|
||||
40% { transform: rotate(-10deg); }
|
||||
50% { transform: rotate(5deg); }
|
||||
60% { transform: rotate(-5deg); }
|
||||
70% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(0deg); }
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
@apply m-0;
|
||||
font-family: var(--font-sans);
|
||||
|
||||
13
src/types/db.d.ts
vendored
13
src/types/db.d.ts
vendored
@@ -36,6 +36,19 @@ declare global {
|
||||
};
|
||||
};
|
||||
|
||||
type NotificationWithUser = Prisma.NotificationGetPayload<{
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true;
|
||||
name: true;
|
||||
email: true;
|
||||
image: true;
|
||||
};
|
||||
};
|
||||
};
|
||||
}>;
|
||||
|
||||
type ReturnError = Error & {
|
||||
code: string;
|
||||
message: string;
|
||||
|
||||
@@ -38,10 +38,21 @@ export type ROLE_NAME = (typeof ROLE_NAME)[keyof typeof ROLE_NAME];
|
||||
|
||||
export const INVITE_STATUS = {
|
||||
PENDING: 'pending',
|
||||
ACCEPT: 'accept',
|
||||
REJECT: 'reject',
|
||||
ACCEPT: 'accepted',
|
||||
REJECT: 'rejected',
|
||||
CANCELED: 'canceled',
|
||||
EXPIRED: 'expired',
|
||||
} as const;
|
||||
|
||||
export type INVITE_STATUS = (typeof INVITE_STATUS)[keyof typeof INVITE_STATUS];
|
||||
|
||||
export const NOTIFICATION_TYPE = {
|
||||
SYSTEM: 'system',
|
||||
ERROR: 'error',
|
||||
INVITATION: 'invitation',
|
||||
HOUSE: 'house',
|
||||
EXPIRED: 'expired',
|
||||
} as const;
|
||||
|
||||
export type NOTIFICATION_TYPE =
|
||||
(typeof NOTIFICATION_TYPE)[keyof typeof NOTIFICATION_TYPE];
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { m } from '@paraglide/messages';
|
||||
|
||||
export function jsonSupport(jsonSTR: string) {
|
||||
try {
|
||||
const data = JSON.parse(jsonSTR);
|
||||
@@ -36,3 +38,28 @@ export function slugify(text: string) {
|
||||
.trim()
|
||||
.replace(/\s+/g, '-');
|
||||
}
|
||||
|
||||
const UNITS = [
|
||||
{ limit: 60, divisor: 1, key: 'common_time_ago_second' },
|
||||
{ limit: 60, divisor: 60, key: 'common_time_ago_minute' },
|
||||
{ limit: 24, divisor: 60 * 60, key: 'common_time_ago_hour' },
|
||||
{ limit: 30, divisor: 60 * 60 * 24, key: 'common_time_ago_day' },
|
||||
{ limit: 12, divisor: 60 * 60 * 24 * 30, key: 'common_time_ago_month' },
|
||||
{ limit: Infinity, divisor: 60 * 60 * 24 * 365, key: 'common_time_ago_year' },
|
||||
] as const;
|
||||
|
||||
export function formatTimeAgo(input: Date | string): string {
|
||||
const date = typeof input === 'string' ? new Date(input) : input;
|
||||
const diffSec = Math.floor((Date.now() - date.getTime()) / 1000);
|
||||
|
||||
if (diffSec < 10) return m.common_time_ago_second({ value: 1 });
|
||||
|
||||
for (const unit of UNITS) {
|
||||
const value = Math.floor(diffSec / unit.divisor);
|
||||
if (value < unit.limit) {
|
||||
return m[unit.key]({ value });
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user