From d7d8e59bc09dd03b3d952ae0ee97979ed0d7674c Mon Sep 17 00:00:00 2001 From: shio <85730998+dino3616@users.noreply.github.com> Date: Sun, 25 Feb 2024 15:17:25 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=E2=9C=A8=20=E6=89=80=E6=9C=89=E8=80=85?= =?UTF-8?q?=E3=82=92=E5=8C=BF=E5=90=8D=E5=8C=96=E3=81=A7=E3=81=8D=E3=82=8B?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/controller/user-mutation.resolver.ts | 14 ++ .../user/use-case/impl/user.use-case.ts | 9 + .../src/module/user/use-case/user.use-case.ts | 1 + apps/locker-dashboard/graphql.schema.json | 49 ++++ apps/website/graphql.schema.json | 49 ++++ .../user-action-status-list.presenter.tsx | 91 ++++---- apps/website/src/common/model/user.ts | 4 +- .../infra/graphql/document/create-user.gql | 1 + .../src/infra/graphql/document/find-user.gql | 1 + .../document/fragment/user-public-meta.gql | 1 + .../document/relate-fingerprint-with-user.gql | 1 + .../graphql/document/report-lost-item.gql | 4 +- .../document/update-user-disclosure.gql | 12 + .../user-dropdown-menu.presenter.tsx | 211 ++++++++++-------- .../pinned-task-section.presenter.tsx | 16 +- .../website/src/use-case/create-user/index.ts | 3 +- .../use-case/find-similar-lost-item/index.ts | 1 + .../use-case/find-user-lost-items/index.ts | 36 +-- apps/website/src/use-case/find-user/index.ts | 1 + .../relate-fingerprint-with-user/index.ts | 1 + .../use-case/update-user-disclosure/index.ts | 37 +++ .../component/information-popover/index.ts | 1 + .../information-popover.presenter.tsx | 18 ++ packages/core/component/switch/index.ts | 1 + .../component/switch/switch.presenter.tsx | 25 +++ packages/core/icon/information-icon/index.ts | 1 + .../information-icon.presenter.tsx | 6 + .../information-icon.story.tsx | 13 ++ .../user-avatar-placeholder-icon/index.ts | 1 + ...user-avatar-placeholder-icon.presenter.tsx | 6 + .../user-avatar-placeholder-icon.story.tsx | 13 ++ packages/core/package.json | 1 + pnpm-lock.yaml | 30 +++ 33 files changed, 499 insertions(+), 160 deletions(-) create mode 100644 apps/website/src/infra/graphql/document/update-user-disclosure.gql create mode 100644 apps/website/src/use-case/update-user-disclosure/index.ts create mode 100644 packages/core/component/information-popover/index.ts create mode 100644 packages/core/component/information-popover/information-popover.presenter.tsx create mode 100644 packages/core/component/switch/index.ts create mode 100644 packages/core/component/switch/switch.presenter.tsx create mode 100644 packages/core/icon/information-icon/index.ts create mode 100644 packages/core/icon/information-icon/information-icon.presenter.tsx create mode 100644 packages/core/icon/information-icon/information-icon.story.tsx create mode 100644 packages/core/icon/user-avatar-placeholder-icon/index.ts create mode 100644 packages/core/icon/user-avatar-placeholder-icon/user-avatar-placeholder-icon.presenter.tsx create mode 100644 packages/core/icon/user-avatar-placeholder-icon/user-avatar-placeholder-icon.story.tsx diff --git a/apps/api/src/module/user/controller/user-mutation.resolver.ts b/apps/api/src/module/user/controller/user-mutation.resolver.ts index d515f60..cb40f13 100644 --- a/apps/api/src/module/user/controller/user-mutation.resolver.ts +++ b/apps/api/src/module/user/controller/user-mutation.resolver.ts @@ -32,6 +32,20 @@ export class UserMutation { return createdUser; } + @Mutation(() => UserObject) + async updateUserDisclosure( + @Args('where', { type: () => UserWhereAuthIdInput }, ValidationPipe) + where: UserWhereAuthIdInput, + @Args('isDiscloseAsOwner', { type: () => Boolean }, ValidationPipe) + isDiscloseAsOwner: boolean, + ): Promise<User> { + this.logger.log(`${this.updateUserDisclosure.name} called`); + + const updatedUser = await this.userUseCase.updateUserDisclosure(where.authId, isDiscloseAsOwner); + + return updatedUser; + } + @Mutation(() => UserObject) async relateFingerprintWithUser( @Args('where', { type: () => UserWhereAuthIdInput }, ValidationPipe) diff --git a/apps/api/src/module/user/use-case/impl/user.use-case.ts b/apps/api/src/module/user/use-case/impl/user.use-case.ts index 728909a..cda7932 100644 --- a/apps/api/src/module/user/use-case/impl/user.use-case.ts +++ b/apps/api/src/module/user/use-case/impl/user.use-case.ts @@ -33,6 +33,15 @@ export class UserUseCase implements UserUseCaseInterface { return createdUser; } + async updateUserDisclosure( + authId: Parameters<UserUseCaseInterface['updateUserDisclosure']>[0], + isDiscloseAsOwner: Parameters<UserUseCaseInterface['updateUserDisclosure']>[1], + ): Promise<User> { + const updatedUser = await this.userRepository.updateByAuthId(authId, { isDiscloseAsOwner }); + + return updatedUser; + } + async relateFingerprintWithUser( authId: Parameters<UserUseCaseInterface['relateFingerprintWithUser']>[0], hashedFingerprintId: Parameters<UserUseCaseInterface['relateFingerprintWithUser']>[1], diff --git a/apps/api/src/module/user/use-case/user.use-case.ts b/apps/api/src/module/user/use-case/user.use-case.ts index 1e94649..c01417c 100644 --- a/apps/api/src/module/user/use-case/user.use-case.ts +++ b/apps/api/src/module/user/use-case/user.use-case.ts @@ -4,5 +4,6 @@ export interface UserUseCaseInterface { findUser(authId: User['authId']): Promise<User | null>; findUserByHashedFingerprintId(hashedFingerprintId: NonNullable<User['hashedFingerprintId']>): Promise<User | null>; createUser(user: Omit<User, 'id' | 'hashedFingerprintId' | 'lostAndFoundState' | 'isDiscloseAsOwner' | 'createdAt' | 'isOnTheWay'>): Promise<User>; + updateUserDisclosure(authId: User['authId'], isDiscloseAsOwner: User['isDiscloseAsOwner']): Promise<User>; relateFingerprintWithUser(authId: User['authId'], hashedFingerprintId: NonNullable<User['hashedFingerprintId']>): Promise<User>; } diff --git a/apps/locker-dashboard/graphql.schema.json b/apps/locker-dashboard/graphql.schema.json index 69b6ba2..82fdec9 100644 --- a/apps/locker-dashboard/graphql.schema.json +++ b/apps/locker-dashboard/graphql.schema.json @@ -767,6 +767,55 @@ }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "updateUserDisclosure", + "description": null, + "args": [ + { + "name": "isDiscloseAsOwner", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "where", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "UserWhereAuthIdInput", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "User", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, diff --git a/apps/website/graphql.schema.json b/apps/website/graphql.schema.json index 69b6ba2..82fdec9 100644 --- a/apps/website/graphql.schema.json +++ b/apps/website/graphql.schema.json @@ -767,6 +767,55 @@ }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "updateUserDisclosure", + "description": null, + "args": [ + { + "name": "isDiscloseAsOwner", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "where", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "UserWhereAuthIdInput", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "User", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, diff --git a/apps/website/src/common/component/user-action-status-list/user-action-status-list.presenter.tsx b/apps/website/src/common/component/user-action-status-list/user-action-status-list.presenter.tsx index bde69c7..57a7cae 100644 --- a/apps/website/src/common/component/user-action-status-list/user-action-status-list.presenter.tsx +++ b/apps/website/src/common/component/user-action-status-list/user-action-status-list.presenter.tsx @@ -1,5 +1,6 @@ import { Image } from '@lockerai/core/component/image'; import { DotIcon } from '@lockerai/core/icon/dot-icon'; +import { UserAvatarPlaceholderIcon } from '@lockerai/core/icon/user-avatar-placeholder-icon'; import { formatDate } from '@lockerai/core/util/format-date'; import type { ComponentPropsWithoutRef, ReactNode } from 'react'; import type { LostItem } from '#website/common/model/lost-item'; @@ -12,40 +13,14 @@ type UserActionStatusListProps = Omit<ComponentPropsWithoutRef<'div'>, 'children lostItem: LostItem; }; -export const UserActionStatusList = ({ user, reporter, owner, lostItem, ...props }: UserActionStatusListProps): ReactNode => ( - <div className="flex flex-col gap-4" {...props}> - <div className="flex items-center gap-3"> - <Image - src={reporter.avatarUrl} - alt="" - width={36} - height={36} - priority - skeleton={{ - className: 'rounded-full', - }} - className="h-9 w-9" - /> - <div className="flex flex-col gap-1"> - <p className="text-base font-bold text-sage-12"> - {reporter.name} - {reporter.id === user?.id ? <span className="text-sage-11"> (You)</span> : null} - </p> - <div className="flex flex-col gap-1 tablet:flex-row tablet:items-center"> - <p className="text-sm text-sage-11 tablet:text-base">{formatDate(lostItem.reportedAt, 'MMM. dd, yyyy HH:mm')} reported</p> - {lostItem.deliveredAt && ( - <> - <DotIcon className="hidden h-4 w-4 fill-sage-11 tablet:inline" /> - <p className="text-sm text-sage-11 tablet:text-base">{formatDate(lostItem.deliveredAt, 'MMM. dd, yyyy HH:mm')} delivered</p> - </> - )} - </div> - </div> - </div> - {owner && lostItem.ownedAt && ( - <div className="flex items-center gap-3" {...props}> +export const UserActionStatusList = ({ user, reporter, owner, lostItem, ...props }: UserActionStatusListProps): ReactNode => { + const isOwnerDisclose = (user && owner && (user.id === owner.id || owner.isDiscloseAsOwner)) ?? undefined; + + return ( + <div className="flex flex-col gap-4" {...props}> + <div className="flex items-center gap-3"> <Image - src={owner.avatarUrl} + src={reporter.avatarUrl} alt="" width={36} height={36} @@ -57,20 +32,54 @@ export const UserActionStatusList = ({ user, reporter, owner, lostItem, ...props /> <div className="flex flex-col gap-1"> <p className="text-base font-bold text-sage-12"> - {owner.name} - {owner.id === user?.id ? <span className="text-sage-11"> (You)</span> : null} + {reporter.name} + {reporter.id === user?.id ? <span className="text-sage-11"> (You)</span> : null} </p> <div className="flex flex-col gap-1 tablet:flex-row tablet:items-center"> - <p className="text-sm text-sage-11 tablet:text-base">{formatDate(lostItem.ownedAt, 'MMM. dd, yyyy HH:mm')} owned</p> - {lostItem.retrievedAt && ( + <p className="text-sm text-sage-11 tablet:text-base">{formatDate(lostItem.reportedAt, 'MMM. dd, yyyy HH:mm')} reported</p> + {lostItem.deliveredAt && ( <> <DotIcon className="hidden h-4 w-4 fill-sage-11 tablet:inline" /> - <p className="text-sm text-sage-11 tablet:text-base">{formatDate(lostItem.retrievedAt, 'MMM. dd, yyyy HH:mm')} retrieved</p> + <p className="text-sm text-sage-11 tablet:text-base">{formatDate(lostItem.deliveredAt, 'MMM. dd, yyyy HH:mm')} delivered</p> </> )} </div> </div> </div> - )} - </div> -); + {owner && lostItem.ownedAt && ( + <div className="flex items-center gap-3" {...props}> + {isOwnerDisclose ? ( + <Image + src={owner.avatarUrl} + alt="" + width={36} + height={36} + priority + skeleton={{ + className: 'rounded-full', + }} + className="h-9 w-9" + /> + ) : ( + <UserAvatarPlaceholderIcon className="h-9 w-9 fill-sage-11" /> + )} + <div className="flex flex-col gap-1"> + <p className="text-base font-bold text-sage-12"> + {isOwnerDisclose ? owner.name : 'Anonymous'} + {owner.id === user?.id ? <span className="text-sage-11"> (You)</span> : null} + </p> + <div className="flex flex-col gap-1 tablet:flex-row tablet:items-center"> + <p className="text-sm text-sage-11 tablet:text-base">{formatDate(lostItem.ownedAt, 'MMM. dd, yyyy HH:mm')} owned</p> + {lostItem.retrievedAt && ( + <> + <DotIcon className="hidden h-4 w-4 fill-sage-11 tablet:inline" /> + <p className="text-sm text-sage-11 tablet:text-base">{formatDate(lostItem.retrievedAt, 'MMM. dd, yyyy HH:mm')} retrieved</p> + </> + )} + </div> + </div> + </div> + )} + </div> + ); +}; diff --git a/apps/website/src/common/model/user.ts b/apps/website/src/common/model/user.ts index 5a86563..edb40fb 100644 --- a/apps/website/src/common/model/user.ts +++ b/apps/website/src/common/model/user.ts @@ -7,10 +7,11 @@ export type User = { email: string; lostAndFoundState: LostAndFoundState; avatarUrl: string; + isDiscloseAsOwner: boolean; createdAt: Date; }; -export type UserPublicMeta = Pick<User, 'id' | 'name' | 'avatarUrl'>; +export type UserPublicMeta = Pick<User, 'id' | 'name' | 'avatarUrl' | 'isDiscloseAsOwner'>; export const mockUser = (user: Partial<User> = {}): User => ({ id: 'e069eeb2-a239-44c7-9870-acc1af492264', @@ -19,6 +20,7 @@ export const mockUser = (user: Partial<User> = {}): User => ({ email: 'example@example.com', lostAndFoundState: 'NONE', avatarUrl: 'https://avatars.githubusercontent.com/u/1', + isDiscloseAsOwner: true, createdAt: new Date(0), ...user, }); diff --git a/apps/website/src/infra/graphql/document/create-user.gql b/apps/website/src/infra/graphql/document/create-user.gql index 0991262..1b59e8c 100644 --- a/apps/website/src/infra/graphql/document/create-user.gql +++ b/apps/website/src/infra/graphql/document/create-user.gql @@ -5,6 +5,7 @@ mutation CreateUser($user: UserCreateInput!) { createdAt email id + isDiscloseAsOwner lostAndFoundState name } diff --git a/apps/website/src/infra/graphql/document/find-user.gql b/apps/website/src/infra/graphql/document/find-user.gql index 3a7d60f..751115d 100644 --- a/apps/website/src/infra/graphql/document/find-user.gql +++ b/apps/website/src/infra/graphql/document/find-user.gql @@ -5,6 +5,7 @@ query FindUser($where: UserWhereAuthIdInput!) { createdAt email id + isDiscloseAsOwner lostAndFoundState name } diff --git a/apps/website/src/infra/graphql/document/fragment/user-public-meta.gql b/apps/website/src/infra/graphql/document/fragment/user-public-meta.gql index b134653..7c509cd 100644 --- a/apps/website/src/infra/graphql/document/fragment/user-public-meta.gql +++ b/apps/website/src/infra/graphql/document/fragment/user-public-meta.gql @@ -1,5 +1,6 @@ fragment UserPublicMeta on User { avatarUrl id + isDiscloseAsOwner name } diff --git a/apps/website/src/infra/graphql/document/relate-fingerprint-with-user.gql b/apps/website/src/infra/graphql/document/relate-fingerprint-with-user.gql index 0286fad..5f64509 100644 --- a/apps/website/src/infra/graphql/document/relate-fingerprint-with-user.gql +++ b/apps/website/src/infra/graphql/document/relate-fingerprint-with-user.gql @@ -5,6 +5,7 @@ mutation RelateFingerprintWithUser($hashedFingerprintId: String!, $where: UserWh createdAt email id + isDiscloseAsOwner lostAndFoundState name } diff --git a/apps/website/src/infra/graphql/document/report-lost-item.gql b/apps/website/src/infra/graphql/document/report-lost-item.gql index 1b13e71..5ea4e91 100644 --- a/apps/website/src/infra/graphql/document/report-lost-item.gql +++ b/apps/website/src/infra/graphql/document/report-lost-item.gql @@ -7,9 +7,7 @@ mutation ReportLostItem($imageFiles: [Upload!]!, $lostItem: LostItemCreateInput! ownedAt reportedAt reporter { - id - name - lostAndFoundState + ...UserPublicMeta } retrievedAt title diff --git a/apps/website/src/infra/graphql/document/update-user-disclosure.gql b/apps/website/src/infra/graphql/document/update-user-disclosure.gql new file mode 100644 index 0000000..dd409ce --- /dev/null +++ b/apps/website/src/infra/graphql/document/update-user-disclosure.gql @@ -0,0 +1,12 @@ +mutation UpdateUserDisclosure($isDiscloseAsOwner: Boolean!, $where: UserWhereAuthIdInput!) { + updateUserDisclosure(isDiscloseAsOwner: $isDiscloseAsOwner, where: $where) { + authId + avatarUrl + createdAt + email + id + isDiscloseAsOwner + lostAndFoundState + name + } +} diff --git a/apps/website/src/layout/global/header/component/user-dropdown-menu/user-dropdown-menu.presenter.tsx b/apps/website/src/layout/global/header/component/user-dropdown-menu/user-dropdown-menu.presenter.tsx index 73d7c16..3b3d04a 100644 --- a/apps/website/src/layout/global/header/component/user-dropdown-menu/user-dropdown-menu.presenter.tsx +++ b/apps/website/src/layout/global/header/component/user-dropdown-menu/user-dropdown-menu.presenter.tsx @@ -1,5 +1,6 @@ 'use client'; +import { InformationPopover } from '#core/component/information-popover'; import { DropdownMenu, DropdownMenuContent, @@ -11,6 +12,8 @@ import { } from '@lockerai/core/component/dropdown-menu'; import { Image } from '@lockerai/core/component/image'; import { Link } from '@lockerai/core/component/link'; +import { toast } from '@lockerai/core/component/sonner'; +import { Switch } from '@lockerai/core/component/switch'; import { Tag } from '@lockerai/core/component/tag'; import { DashboardIcon } from '@lockerai/core/icon/dashboard-icon'; import { ExternalLinkIcon } from '@lockerai/core/icon/external-link-icon'; @@ -20,103 +23,131 @@ import { SearchIcon } from '@lockerai/core/icon/search-icon'; import { SignOutIcon } from '@lockerai/core/icon/sign-out-icon'; import { UploadIcon } from '@lockerai/core/icon/upload-icon'; import { cn } from '@lockerai/tailwind'; -import type { ComponentPropsWithoutRef, ReactNode } from 'react'; +import { type ComponentPropsWithoutRef, type ReactNode, useState } from 'react'; import type { User } from '#website/common/model/user'; +import { updateUserDisclosureUseCase } from '#website/use-case/update-user-disclosure'; type UserDropdownMenuProps = Omit<ComponentPropsWithoutRef<'button'>, 'children' | 'className'> & { user: User; signOut: () => Promise<void>; }; -export const UserDropdownMenu = ({ user, signOut, ...props }: UserDropdownMenuProps): ReactNode => ( - <DropdownMenu> - <DropdownMenuTrigger asChild> - <button className="relative h-10 w-10 rounded-full drop-shadow-md" {...props}> - <Image - aria-hidden - src={user.avatarUrl} - alt="" - width={40} - height={40} - skeleton={{ - className: cn('absolute inset-0 overflow-visible before:hidden'), - }} - className="absolute inset-0 h-full w-full rounded-full blur-sm" - /> - <Image - src={user.avatarUrl} - alt="Your avatar image." - width={40} - height={40} - skeleton={{ - className: 'rounded-full', - }} - className="h-10 w-10" - /> - </button> - </DropdownMenuTrigger> - <DropdownMenuContent align="end"> - <DropdownMenuLabel className="flex items-center gap-6"> - <div className="flex flex-col gap-1"> - <p className="text-base font-bold text-sage-12">{user.name}</p> - <p className="text-sm text-sage-11">{user.email}</p> - </div> - {user.lostAndFoundState !== 'NONE' ? ( - <Tag variant={{ color: user.lostAndFoundState === 'DELIVERING' ? 'purple' : 'cyan' }}>{user.lostAndFoundState.toLowerCase()}</Tag> - ) : null} - </DropdownMenuLabel> - <DropdownMenuSeparator /> - <DropdownMenuGroup> - <DropdownMenuItem asChild> - <Link href="/dashboard"> - <DashboardIcon className="h-4 w-4 fill-sage-11 transition group-hover:fill-sage-12" /> - Dashboard - </Link> - </DropdownMenuItem> - <DropdownMenuItem asChild> - <Link href="/search"> - <SearchIcon className="h-4 w-4 fill-sage-11 transition group-hover:fill-sage-12" /> - Search lost item - </Link> - </DropdownMenuItem> - <DropdownMenuItem asChild> - <Link href="/report"> - <UploadIcon className="h-4 w-4 stroke-sage-11 transition group-hover:stroke-sage-12" /> - Report dropped lost item - </Link> - </DropdownMenuItem> - </DropdownMenuGroup> - <DropdownMenuSeparator /> - <DropdownMenuGroup> - <DropdownMenuItem asChild> - <Link href="https://github.com/nitic-pbl-p8/lockerai" external> - <GithubIcon className="h-4 w-4 fill-sage-11 transition group-hover:fill-sage-12" /> - GitHub - <ExternalLinkIcon className="ml-auto h-4 w-4 stroke-sage-11 transition group-hover:stroke-sage-12" /> - </Link> - </DropdownMenuItem> +export const UserDropdownMenu = ({ user, signOut, ...props }: UserDropdownMenuProps): ReactNode => { + const [loading, setLoading] = useState(false); + + return ( + <DropdownMenu> + <DropdownMenuTrigger asChild> + <button className="relative h-10 w-10 rounded-full drop-shadow-md" {...props}> + <Image + aria-hidden + src={user.avatarUrl} + alt="" + width={40} + height={40} + skeleton={{ + className: cn('absolute inset-0 overflow-visible before:hidden'), + }} + className="absolute inset-0 h-full w-full rounded-full blur-sm" + /> + <Image + src={user.avatarUrl} + alt="Your avatar image." + width={40} + height={40} + skeleton={{ + className: 'rounded-full', + }} + className="h-10 w-10" + /> + </button> + </DropdownMenuTrigger> + <DropdownMenuContent align="end"> + <DropdownMenuLabel className="flex items-center gap-6"> + <div className="flex flex-col gap-1"> + <p className="text-base font-bold text-sage-12">{user.name}</p> + <p className="text-sm text-sage-11">{user.email}</p> + </div> + {user.lostAndFoundState !== 'NONE' ? ( + <Tag variant={{ color: user.lostAndFoundState === 'DELIVERING' ? 'purple' : 'cyan' }}>{user.lostAndFoundState.toLowerCase()}</Tag> + ) : null} + </DropdownMenuLabel> + <DropdownMenuSeparator /> + <DropdownMenuGroup> + <DropdownMenuItem asChild> + <Link href="/dashboard"> + <DashboardIcon className="h-4 w-4 fill-sage-11 transition group-hover:fill-sage-12" /> + Dashboard + </Link> + </DropdownMenuItem> + <DropdownMenuItem asChild> + <Link href="/search"> + <SearchIcon className="h-4 w-4 fill-sage-11 transition group-hover:fill-sage-12" /> + Search lost item + </Link> + </DropdownMenuItem> + <DropdownMenuItem asChild> + <Link href="/report"> + <UploadIcon className="h-4 w-4 stroke-sage-11 transition group-hover:stroke-sage-12" /> + Report dropped lost item + </Link> + </DropdownMenuItem> + </DropdownMenuGroup> + <DropdownMenuSeparator /> + <DropdownMenuGroup> + <DropdownMenuItem asChild> + <Link href="https://github.com/nitic-pbl-p8/lockerai" external> + <GithubIcon className="h-4 w-4 fill-sage-11 transition group-hover:fill-sage-12" /> + GitHub + <ExternalLinkIcon className="ml-auto h-4 w-4 stroke-sage-11 transition group-hover:stroke-sage-12" /> + </Link> + </DropdownMenuItem> + <DropdownMenuItem asChild> + <Link + href="https://www.figma.com/file/xNKAhniAfPPTsL987xRCVe/website?type=design&node-id=20%3A35&mode=design&t=oAlQP6Jqqk0ZcqOy-1" + external + > + <FigmaIcon className="h-4 w-4 fill-sage-11 transition group-hover:fill-sage-12" /> + Figma + <ExternalLinkIcon className="ml-auto h-4 w-4 stroke-sage-11 transition group-hover:stroke-sage-12" /> + </Link> + </DropdownMenuItem> + </DropdownMenuGroup> + <DropdownMenuSeparator /> + <DropdownMenuGroup> + <DropdownMenuLabel className="flex items-center justify-between gap-6 px-3 py-2"> + <div className="flex items-center gap-3"> + <p className="text-base font-bold text-sage-11">Disclosure</p> + <InformationPopover description="If on, your name and email will be disclosed to the user who reported your lost item. If it is off, it will not be disclosed and will be anonymized." /> + </div> + <Switch + disabled={loading} + defaultChecked={user.isDiscloseAsOwner} + onCheckedChange={async (checked) => { + setLoading(true); + + await updateUserDisclosureUseCase(user.authId, checked); + toast.success('Disclosure has been updated', { + description: `Your name and email address will now be ${checked ? 'disclosed' : 'hidden'} to the user who reported your lost item.`, + }); + + setLoading(false); + }} + /> + </DropdownMenuLabel> + </DropdownMenuGroup> + <DropdownMenuSeparator /> <DropdownMenuItem asChild> - <Link - href="https://www.figma.com/file/xNKAhniAfPPTsL987xRCVe/website?type=design&node-id=20%3A35&mode=design&t=oAlQP6Jqqk0ZcqOy-1" - external + <button + onClick={async () => { + await signOut(); + }} > - <FigmaIcon className="h-4 w-4 fill-sage-11 transition group-hover:fill-sage-12" /> - Figma - <ExternalLinkIcon className="ml-auto h-4 w-4 stroke-sage-11 transition group-hover:stroke-sage-12" /> - </Link> + <SignOutIcon className="h-4 w-4 stroke-sage-11 transition group-hover:stroke-sage-12" /> + Sign out + </button> </DropdownMenuItem> - </DropdownMenuGroup> - <DropdownMenuSeparator /> - <DropdownMenuItem asChild> - <button - onClick={async () => { - await signOut(); - }} - > - <SignOutIcon className="h-4 w-4 stroke-sage-11 transition group-hover:stroke-sage-12" /> - Sign out - </button> - </DropdownMenuItem> - </DropdownMenuContent> - </DropdownMenu> -); + </DropdownMenuContent> + </DropdownMenu> + ); +}; diff --git a/apps/website/src/module/dashboard/pinned-task-section/pinned-task-section.presenter.tsx b/apps/website/src/module/dashboard/pinned-task-section/pinned-task-section.presenter.tsx index dd37581..9d7ea92 100644 --- a/apps/website/src/module/dashboard/pinned-task-section/pinned-task-section.presenter.tsx +++ b/apps/website/src/module/dashboard/pinned-task-section/pinned-task-section.presenter.tsx @@ -50,9 +50,9 @@ export const PinnedTaskSection = ({ user, currentTargetLostItem, variant, ...pro const { beacon, heading } = pinnedTaskSectionVariant({ ...variant }); return ( - <section className="flex flex-col items-center gap-10 px-6 py-10 tablet:px-16 tablet:py-12" {...props}> - <div className="flex flex-col items-center gap-3 tablet:gap-5"> - <h1 className="flex w-fit flex-col items-center gap-6 tablet:flex-row"> + <section className="flex flex-col items-center gap-10 px-6 py-10 laptop:px-16 laptop:py-12" {...props}> + <div className="flex flex-col items-center gap-3 laptop:gap-5"> + <h1 className="flex w-fit flex-col items-center gap-6 laptop:flex-row"> <span className={cn('relative h-6 w-6')}> <span className={cn( @@ -62,16 +62,16 @@ export const PinnedTaskSection = ({ user, currentTargetLostItem, variant, ...pro )} /> </span> - <span className="text-center text-4xl font-bold text-sage-12 tablet:text-5xl"> + <span className="text-center text-4xl font-bold text-sage-12 laptop:text-5xl"> You are currently <span className={heading()}>{user.lostAndFoundState.toLowerCase()}</span> </span> </h1> - <p className="max-w-[820px] text-xl text-sage-11 tablet:text-2xl"> + <p className="max-w-[820px] text-xl text-sage-11 laptop:text-2xl"> You are in the process of {user.lostAndFoundState.toLowerCase()} a lost item. Please go to the nearest locker and{' '} {user.lostAndFoundState === 'DELIVERING' ? 'put in' : 'take out'} the lost item. </p> </div> - <div className="flex flex-col items-center gap-10 tablet:flex-row"> + <div className="flex flex-col items-center gap-10 laptop:flex-row"> <figure className="shrink-0"> <Image src={currentTargetLostItem.lostItem.imageUrls[0]!} @@ -86,8 +86,8 @@ export const PinnedTaskSection = ({ user, currentTargetLostItem, variant, ...pro </figure> <div className="flex w-fit shrink flex-col gap-7"> <hgroup className="flex flex-col gap-2"> - <h2 className="text-2xl font-bold text-sage-12 tablet:text-3xl">{currentTargetLostItem.lostItem.title}</h2> - <p className="text-base text-sage-11 tablet:text-lg">{currentTargetLostItem.lostItem.description}</p> + <h2 className="text-2xl font-bold text-sage-12 laptop:text-3xl">{currentTargetLostItem.lostItem.title}</h2> + <p className="text-base text-sage-11 laptop:text-lg">{currentTargetLostItem.lostItem.description}</p> </hgroup> <UserActionStatusList user={user} diff --git a/apps/website/src/use-case/create-user/index.ts b/apps/website/src/use-case/create-user/index.ts index 54470e3..c66f5b5 100644 --- a/apps/website/src/use-case/create-user/index.ts +++ b/apps/website/src/use-case/create-user/index.ts @@ -2,7 +2,7 @@ import type { User } from '#website/common/model/user'; import { CreateUserDocument, type CreateUserMutation, type CreateUserMutationVariables } from '#website/infra/graphql/generated/graphql'; import { urqlClient } from '#website/infra/urql'; -type CreateUserUseCaseInput = Omit<User, 'id' | 'lostAndFoundState' | 'createdAt'>; +type CreateUserUseCaseInput = Omit<User, 'id' | 'lostAndFoundState' | 'isDiscloseAsOwner' | 'createdAt'>; type CreateUserUseCase = (user: CreateUserUseCaseInput) => Promise<User>; @@ -26,6 +26,7 @@ export const createUserUseCase: CreateUserUseCase = async ({ authId, name, email email: data.createUser.email, lostAndFoundState: data.createUser.lostAndFoundState, avatarUrl: data.createUser.avatarUrl, + isDiscloseAsOwner: data.createUser.isDiscloseAsOwner, createdAt: data.createUser.createdAt, }; }; diff --git a/apps/website/src/use-case/find-similar-lost-item/index.ts b/apps/website/src/use-case/find-similar-lost-item/index.ts index e3dc4af..2f1ccef 100644 --- a/apps/website/src/use-case/find-similar-lost-item/index.ts +++ b/apps/website/src/use-case/find-similar-lost-item/index.ts @@ -50,6 +50,7 @@ export const findSimilarLostItemUseCase: FindSimilarLostItemUseCase = async (des id: data.findSimilarLostItem.reporter.id, name: data.findSimilarLostItem.reporter.name, avatarUrl: data.findSimilarLostItem.reporter.avatarUrl, + isDiscloseAsOwner: data.findSimilarLostItem.reporter.isDiscloseAsOwner, }; return { diff --git a/apps/website/src/use-case/find-user-lost-items/index.ts b/apps/website/src/use-case/find-user-lost-items/index.ts index 2cae3cc..541d58f 100644 --- a/apps/website/src/use-case/find-user-lost-items/index.ts +++ b/apps/website/src/use-case/find-user-lost-items/index.ts @@ -47,21 +47,23 @@ export const findUserLostItemsUseCase: FindUserLostItemsUseCase = async (authId) title: data.findUser!.reportedLostItems[0]!.title, description: data.findUser!.reportedLostItems[0]!.description, imageUrls: data.findUser!.reportedLostItems[0]!.imageUrls, - reportedAt: new Date(data.findUser!.reportedLostItems[0]!.reportedAt), - ownedAt: data.findUser!.reportedLostItems[0]!.ownedAt ? new Date(data.findUser!.reportedLostItems[0]!.ownedAt) : null, - deliveredAt: data.findUser!.reportedLostItems[0]!.deliveredAt ? new Date(data.findUser!.reportedLostItems[0]!.deliveredAt) : null, - retrievedAt: data.findUser!.reportedLostItems[0]!.retrievedAt ? new Date(data.findUser!.reportedLostItems[0]!.retrievedAt) : null, + reportedAt: data.findUser!.reportedLostItems[0]!.reportedAt, + ownedAt: data.findUser!.reportedLostItems[0]!.ownedAt ? data.findUser!.reportedLostItems[0]!.ownedAt : null, + deliveredAt: data.findUser!.reportedLostItems[0]!.deliveredAt ? data.findUser!.reportedLostItems[0]!.deliveredAt : null, + retrievedAt: data.findUser!.reportedLostItems[0]!.retrievedAt ? data.findUser!.reportedLostItems[0]!.retrievedAt : null, }, reporter: { id: data.findUser!.reportedLostItems[0]!.reporter.id, name: data.findUser!.reportedLostItems[0]!.reporter.name, avatarUrl: data.findUser!.reportedLostItems[0]!.reporter.avatarUrl, + isDiscloseAsOwner: data.findUser!.reportedLostItems[0]!.reporter.isDiscloseAsOwner, }, owner: data.findUser!.reportedLostItems[0]!.owner ? { id: data.findUser!.reportedLostItems[0]!.owner.id, name: data.findUser!.reportedLostItems[0]!.owner.name, avatarUrl: data.findUser!.reportedLostItems[0]!.owner.avatarUrl, + isDiscloseAsOwner: data.findUser!.reportedLostItems[0]!.owner.isDiscloseAsOwner, } : null, })) @@ -71,21 +73,23 @@ export const findUserLostItemsUseCase: FindUserLostItemsUseCase = async (authId) title: data.findUser!.ownedLostItems[0]!.title, description: data.findUser!.ownedLostItems[0]!.description, imageUrls: data.findUser!.ownedLostItems[0]!.imageUrls, - reportedAt: new Date(data.findUser!.ownedLostItems[0]!.reportedAt), - ownedAt: data.findUser!.ownedLostItems[0]!.ownedAt ? new Date(data.findUser!.ownedLostItems[0]!.ownedAt) : null, - deliveredAt: data.findUser!.ownedLostItems[0]!.deliveredAt ? new Date(data.findUser!.ownedLostItems[0]!.deliveredAt) : null, - retrievedAt: data.findUser!.ownedLostItems[0]!.retrievedAt ? new Date(data.findUser!.ownedLostItems[0]!.retrievedAt) : null, + reportedAt: data.findUser!.ownedLostItems[0]!.reportedAt, + ownedAt: data.findUser!.ownedLostItems[0]!.ownedAt ? data.findUser!.ownedLostItems[0]!.ownedAt : null, + deliveredAt: data.findUser!.ownedLostItems[0]!.deliveredAt ? data.findUser!.ownedLostItems[0]!.deliveredAt : null, + retrievedAt: data.findUser!.ownedLostItems[0]!.retrievedAt ? data.findUser!.ownedLostItems[0]!.retrievedAt : null, }, reporter: { id: data.findUser!.ownedLostItems[0]!.reporter.id, name: data.findUser!.ownedLostItems[0]!.reporter.name, avatarUrl: data.findUser!.ownedLostItems[0]!.reporter.avatarUrl, + isDiscloseAsOwner: data.findUser!.ownedLostItems[0]!.reporter.isDiscloseAsOwner, }, owner: data.findUser!.ownedLostItems[0]!.owner ? { id: data.findUser!.ownedLostItems[0]!.owner.id, name: data.findUser!.ownedLostItems[0]!.owner.name, avatarUrl: data.findUser!.ownedLostItems[0]!.owner.avatarUrl, + isDiscloseAsOwner: data.findUser!.ownedLostItems[0]!.owner.isDiscloseAsOwner, } : null, })) @@ -96,10 +100,10 @@ export const findUserLostItemsUseCase: FindUserLostItemsUseCase = async (authId) title: reportedLostItem.title, description: reportedLostItem.description, imageUrls: reportedLostItem.imageUrls, - reportedAt: new Date(reportedLostItem.reportedAt), - ownedAt: reportedLostItem.ownedAt ? new Date(reportedLostItem.ownedAt) : null, - deliveredAt: reportedLostItem.deliveredAt ? new Date(reportedLostItem.deliveredAt) : null, - retrievedAt: reportedLostItem.retrievedAt ? new Date(reportedLostItem.retrievedAt) : null, + reportedAt: reportedLostItem.reportedAt, + ownedAt: reportedLostItem.ownedAt ? reportedLostItem.ownedAt : null, + deliveredAt: reportedLostItem.deliveredAt ? reportedLostItem.deliveredAt : null, + retrievedAt: reportedLostItem.retrievedAt ? reportedLostItem.retrievedAt : null, })); const ownedLostItems: LostItem[] = data.findUser.ownedLostItems.map((ownedLostItem) => ({ @@ -107,10 +111,10 @@ export const findUserLostItemsUseCase: FindUserLostItemsUseCase = async (authId) title: ownedLostItem.title, description: ownedLostItem.description, imageUrls: ownedLostItem.imageUrls, - reportedAt: new Date(ownedLostItem.reportedAt), - ownedAt: ownedLostItem.ownedAt ? new Date(ownedLostItem.ownedAt) : null, - deliveredAt: ownedLostItem.deliveredAt ? new Date(ownedLostItem.deliveredAt) : null, - retrievedAt: ownedLostItem.retrievedAt ? new Date(ownedLostItem.retrievedAt) : null, + reportedAt: ownedLostItem.reportedAt, + ownedAt: ownedLostItem.ownedAt ? ownedLostItem.ownedAt : null, + deliveredAt: ownedLostItem.deliveredAt ? ownedLostItem.deliveredAt : null, + retrievedAt: ownedLostItem.retrievedAt ? ownedLostItem.retrievedAt : null, })); return { diff --git a/apps/website/src/use-case/find-user/index.ts b/apps/website/src/use-case/find-user/index.ts index 930cffc..86ea6ee 100644 --- a/apps/website/src/use-case/find-user/index.ts +++ b/apps/website/src/use-case/find-user/index.ts @@ -29,6 +29,7 @@ export const findUserUseCase: FindUserUseCase = async (authId) => { email: data.findUser.email, lostAndFoundState: data.findUser.lostAndFoundState, avatarUrl: data.findUser.avatarUrl, + isDiscloseAsOwner: data.findUser.isDiscloseAsOwner, createdAt: data.findUser.createdAt, }; }; diff --git a/apps/website/src/use-case/relate-fingerprint-with-user/index.ts b/apps/website/src/use-case/relate-fingerprint-with-user/index.ts index 2890bc9..77a4cfe 100644 --- a/apps/website/src/use-case/relate-fingerprint-with-user/index.ts +++ b/apps/website/src/use-case/relate-fingerprint-with-user/index.ts @@ -29,6 +29,7 @@ export const relateFingerprintWithUserUseCase: RelateFingerprintWithUserUseCase email: data.relateFingerprintWithUser.email, lostAndFoundState: data.relateFingerprintWithUser.lostAndFoundState, avatarUrl: data.relateFingerprintWithUser.avatarUrl, + isDiscloseAsOwner: data.relateFingerprintWithUser.isDiscloseAsOwner, createdAt: data.relateFingerprintWithUser.createdAt, }; }; diff --git a/apps/website/src/use-case/update-user-disclosure/index.ts b/apps/website/src/use-case/update-user-disclosure/index.ts new file mode 100644 index 0000000..29006e0 --- /dev/null +++ b/apps/website/src/use-case/update-user-disclosure/index.ts @@ -0,0 +1,37 @@ +'use server'; + +import type { User } from '#website/common/model/user'; +import { + UpdateUserDisclosureDocument, + type UpdateUserDisclosureMutation, + type UpdateUserDisclosureMutationVariables, +} from '#website/infra/graphql/generated/graphql'; +import { urqlClient } from '#website/infra/urql'; + +type UpdateUserDisclosureUseCase = (authId: User['authId'], isDiscloseAsOwner: boolean) => Promise<User>; + +export const updateUserDisclosureUseCase: UpdateUserDisclosureUseCase = async (authId, isDiscloseAsOwner) => { + const { data, error } = await urqlClient.mutation<UpdateUserDisclosureMutation, UpdateUserDisclosureMutationVariables>( + UpdateUserDisclosureDocument, + { + where: { + authId, + }, + isDiscloseAsOwner, + }, + ); + if (!data || error) { + throw error || new Error('Failed to update user disclosure.'); + } + + return { + id: data.updateUserDisclosure.id, + authId: data.updateUserDisclosure.authId, + name: data.updateUserDisclosure.name, + email: data.updateUserDisclosure.email, + lostAndFoundState: data.updateUserDisclosure.lostAndFoundState, + avatarUrl: data.updateUserDisclosure.avatarUrl, + isDiscloseAsOwner: data.updateUserDisclosure.isDiscloseAsOwner, + createdAt: data.updateUserDisclosure.createdAt, + }; +}; diff --git a/packages/core/component/information-popover/index.ts b/packages/core/component/information-popover/index.ts new file mode 100644 index 0000000..83701ae --- /dev/null +++ b/packages/core/component/information-popover/index.ts @@ -0,0 +1 @@ +export { InformationPopover } from './information-popover.presenter'; diff --git a/packages/core/component/information-popover/information-popover.presenter.tsx b/packages/core/component/information-popover/information-popover.presenter.tsx new file mode 100644 index 0000000..42b563d --- /dev/null +++ b/packages/core/component/information-popover/information-popover.presenter.tsx @@ -0,0 +1,18 @@ +import { type ComponentPropsWithoutRef, type ReactNode } from 'react'; +import { Popover, PopoverContent, PopoverTrigger } from '#core/component/popover'; +import { InformationIcon } from '#core/icon/information-icon'; + +type InformationPopoverProps = Omit<ComponentPropsWithoutRef<typeof Popover>, 'children'> & { + description: string; +}; + +export const InformationPopover = ({ description, ...props }: InformationPopoverProps): ReactNode => ( + <Popover {...props}> + <PopoverTrigger className="transition hover:opacity-80"> + <InformationIcon className="h-3 w-3 fill-sage-11" /> + </PopoverTrigger> + <PopoverContent> + <p className="text-sm text-sage-11">{description}</p> + </PopoverContent> + </Popover> +); diff --git a/packages/core/component/switch/index.ts b/packages/core/component/switch/index.ts new file mode 100644 index 0000000..05aaf3f --- /dev/null +++ b/packages/core/component/switch/index.ts @@ -0,0 +1 @@ +export { Switch } from './switch.presenter'; diff --git a/packages/core/component/switch/switch.presenter.tsx b/packages/core/component/switch/switch.presenter.tsx new file mode 100644 index 0000000..b3235a6 --- /dev/null +++ b/packages/core/component/switch/switch.presenter.tsx @@ -0,0 +1,25 @@ +'use client'; + +import { cn } from '@lockerai/tailwind'; +import * as RadixUiSwitch from '@radix-ui/react-switch'; +import { type ComponentPropsWithoutRef, type ElementRef, forwardRef } from 'react'; + +type SelectContentProps = Omit<ComponentPropsWithoutRef<typeof RadixUiSwitch.Root>, 'className'>; + +export const Switch = forwardRef<ElementRef<typeof RadixUiSwitch.Root>, Omit<SelectContentProps, 'ref'>>(({ ...props }, ref) => ( + <RadixUiSwitch.Root + className={cn( + 'peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-4 border-transparent shadow-inner transition disabled:cursor-not-allowed disabled:opacity-50 rdx-state-checked:bg-green-11 rdx-state-unchecked:bg-sage-11', + )} + {...props} + ref={ref} + > + <RadixUiSwitch.Thumb + className={cn( + 'pointer-events-none block h-4 w-4 rounded-full bg-sage-3 shadow-lg ring-0 transition rdx-state-checked:translate-x-5 rdx-state-unchecked:translate-x-0', + )} + /> + </RadixUiSwitch.Root> +)); + +Switch.displayName = RadixUiSwitch.Root.displayName; diff --git a/packages/core/icon/information-icon/index.ts b/packages/core/icon/information-icon/index.ts new file mode 100644 index 0000000..7ef7c74 --- /dev/null +++ b/packages/core/icon/information-icon/index.ts @@ -0,0 +1 @@ +export { InformationIcon } from './information-icon.presenter'; diff --git a/packages/core/icon/information-icon/information-icon.presenter.tsx b/packages/core/icon/information-icon/information-icon.presenter.tsx new file mode 100644 index 0000000..af3f109 --- /dev/null +++ b/packages/core/icon/information-icon/information-icon.presenter.tsx @@ -0,0 +1,6 @@ +import type { ComponentPropsWithoutRef, ReactNode } from 'react'; +import { BsInfoCircleFill } from 'react-icons/bs'; + +type InformationIconProps = Omit<ComponentPropsWithoutRef<'svg'>, 'children'>; + +export const InformationIcon = ({ ...props }: InformationIconProps): ReactNode => <BsInfoCircleFill {...props} />; diff --git a/packages/core/icon/information-icon/information-icon.story.tsx b/packages/core/icon/information-icon/information-icon.story.tsx new file mode 100644 index 0000000..d632eb9 --- /dev/null +++ b/packages/core/icon/information-icon/information-icon.story.tsx @@ -0,0 +1,13 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { InformationIcon } from './information-icon.presenter'; + +type Story = StoryObj<typeof InformationIcon>; + +const meta = { + component: InformationIcon, + argTypes: {}, +} satisfies Meta<typeof InformationIcon>; + +export default meta; + +export const Default: Story = {}; diff --git a/packages/core/icon/user-avatar-placeholder-icon/index.ts b/packages/core/icon/user-avatar-placeholder-icon/index.ts new file mode 100644 index 0000000..d412b2f --- /dev/null +++ b/packages/core/icon/user-avatar-placeholder-icon/index.ts @@ -0,0 +1 @@ +export { UserAvatarPlaceholderIcon } from './user-avatar-placeholder-icon.presenter'; diff --git a/packages/core/icon/user-avatar-placeholder-icon/user-avatar-placeholder-icon.presenter.tsx b/packages/core/icon/user-avatar-placeholder-icon/user-avatar-placeholder-icon.presenter.tsx new file mode 100644 index 0000000..624e6fb --- /dev/null +++ b/packages/core/icon/user-avatar-placeholder-icon/user-avatar-placeholder-icon.presenter.tsx @@ -0,0 +1,6 @@ +import type { ComponentPropsWithoutRef, ReactNode } from 'react'; +import { FaCircleUser } from 'react-icons/fa6'; + +type UserAvatarPlaceholderIconProps = Omit<ComponentPropsWithoutRef<'svg'>, 'children'>; + +export const UserAvatarPlaceholderIcon = ({ ...props }: UserAvatarPlaceholderIconProps): ReactNode => <FaCircleUser {...props} />; diff --git a/packages/core/icon/user-avatar-placeholder-icon/user-avatar-placeholder-icon.story.tsx b/packages/core/icon/user-avatar-placeholder-icon/user-avatar-placeholder-icon.story.tsx new file mode 100644 index 0000000..cd02275 --- /dev/null +++ b/packages/core/icon/user-avatar-placeholder-icon/user-avatar-placeholder-icon.story.tsx @@ -0,0 +1,13 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { UserAvatarPlaceholderIcon } from './user-avatar-placeholder-icon.presenter'; + +type Story = StoryObj<typeof UserAvatarPlaceholderIcon>; + +const meta = { + component: UserAvatarPlaceholderIcon, + argTypes: {}, +} satisfies Meta<typeof UserAvatarPlaceholderIcon>; + +export default meta; + +export const Default: Story = {}; diff --git a/packages/core/package.json b/packages/core/package.json index 2921e6d..18d93f4 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -20,6 +20,7 @@ "@radix-ui/react-dropdown-menu": "2.0.6", "@radix-ui/react-popover": "1.0.7", "@radix-ui/react-select": "2.0.0", + "@radix-ui/react-switch": "1.0.3", "date-fns": "2.30.0", "date-fns-tz": "2.0.0", "dotenv": "16.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 671fe73..b423416 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -506,6 +506,9 @@ importers: '@radix-ui/react-select': specifier: 2.0.0 version: 2.0.0(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-switch': + specifier: 1.0.3 + version: 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0) date-fns: specifier: 2.30.0 version: 2.30.0 @@ -5648,6 +5651,33 @@ packages: '@types/react': 18.2.47 react: 18.2.0 + /@radix-ui/react-switch@1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-mxm87F88HyHztsI7N+ZUmEoARGkC22YVW5CaC+Byc+HRpuvCrOBPTAnXgf+tZ/7i0Sg/eOePGdMhUKhPaQEqow==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.8 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.47)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.47)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.47)(react@18.2.0) + '@radix-ui/react-use-previous': 1.0.1(@types/react@18.2.47)(react@18.2.0) + '@radix-ui/react-use-size': 1.0.1(@types/react@18.2.47)(react@18.2.0) + '@types/react': 18.2.47 + '@types/react-dom': 18.2.18 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-toggle-group@1.0.4(@types/react-dom@18.2.18)(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-Uaj/M/cMyiyT9Bx6fOZO0SAG4Cls0GptBWiBmBxofmDbNVnYYoyRWj/2M/6VCi/7qcXFWnHhRUfdfZFvvkuu8A==} peerDependencies: