rusty-replay 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/.eslintrc.js +10 -0
  2. package/.vscode/settings.json +3 -0
  3. package/README.md +92 -0
  4. package/apps/web/README.md +11 -0
  5. package/apps/web/api/auth/keys.ts +3 -0
  6. package/apps/web/api/auth/types.ts +25 -0
  7. package/apps/web/api/auth/use-query-profile.ts +19 -0
  8. package/apps/web/api/auth/use-sign-in.ts +24 -0
  9. package/apps/web/api/axios.ts +122 -0
  10. package/apps/web/api/error-code.ts +36 -0
  11. package/apps/web/api/event/keys.ts +14 -0
  12. package/apps/web/api/event/types.ts +91 -0
  13. package/apps/web/api/event/use-mutation-event-assignee.ts +103 -0
  14. package/apps/web/api/event/use-mutation-event-priority.ts +97 -0
  15. package/apps/web/api/event/use-mutation-event-status.ts +198 -0
  16. package/apps/web/api/event/use-query-event-detail.ts +25 -0
  17. package/apps/web/api/event/use-query-event-list.ts +42 -0
  18. package/apps/web/api/health-check/index.ts +21 -0
  19. package/apps/web/api/project/keys.ts +4 -0
  20. package/apps/web/api/project/types.ts +28 -0
  21. package/apps/web/api/project/use-create-project.ts +30 -0
  22. package/apps/web/api/project/use-query-project-list.ts +19 -0
  23. package/apps/web/api/project/use-query-project-users.ts +23 -0
  24. package/apps/web/api/types.ts +44 -0
  25. package/apps/web/app/(auth)/layout.tsx +5 -0
  26. package/apps/web/app/(auth)/sign-in/page.tsx +20 -0
  27. package/apps/web/app/(auth)/sign-up/page.tsx +5 -0
  28. package/apps/web/app/(project)/project/[project_id]/issues/[issue_id]/page.tsx +17 -0
  29. package/apps/web/app/(project)/project/[project_id]/issues/page.tsx +15 -0
  30. package/apps/web/app/(project)/project/[project_id]/page.tsx +10 -0
  31. package/apps/web/app/(project)/project/page.tsx +10 -0
  32. package/apps/web/app/(report)/error-list/page.tsx +7 -0
  33. package/apps/web/app/favicon.ico +0 -0
  34. package/apps/web/app/layout.tsx +35 -0
  35. package/apps/web/app/page.tsx +3 -0
  36. package/apps/web/components/.gitkeep +0 -0
  37. package/apps/web/components/event-list/event-detail.tsx +242 -0
  38. package/apps/web/components/event-list/event-list.tsx +376 -0
  39. package/apps/web/components/event-list/preview.tsx +573 -0
  40. package/apps/web/components/layouts/default-layout.tsx +59 -0
  41. package/apps/web/components/login-form.tsx +124 -0
  42. package/apps/web/components/project/create-project.tsx +130 -0
  43. package/apps/web/components/project/hooks/use-get-event-params.ts +9 -0
  44. package/apps/web/components/project/hooks/use-get-project-params.ts +10 -0
  45. package/apps/web/components/project/project-detail.tsx +240 -0
  46. package/apps/web/components/project/project-list.tsx +137 -0
  47. package/apps/web/components/providers.tsx +25 -0
  48. package/apps/web/components/ui/assignee-dropdown.tsx +176 -0
  49. package/apps/web/components/ui/event-status-dropdown.tsx +104 -0
  50. package/apps/web/components/ui/priority-dropdown.tsx +123 -0
  51. package/apps/web/components/widget/app-sidebar.tsx +225 -0
  52. package/apps/web/components/widget/nav-main.tsx +73 -0
  53. package/apps/web/components/widget/nav-projects.tsx +84 -0
  54. package/apps/web/components/widget/nav-user.tsx +113 -0
  55. package/apps/web/components.json +20 -0
  56. package/apps/web/constants/routes.ts +12 -0
  57. package/apps/web/eslint.config.js +4 -0
  58. package/apps/web/hooks/use-boolean-state.ts +13 -0
  59. package/apps/web/lib/.gitkeep +0 -0
  60. package/apps/web/next-env.d.ts +5 -0
  61. package/apps/web/next.config.mjs +6 -0
  62. package/apps/web/package.json +60 -0
  63. package/apps/web/postcss.config.mjs +1 -0
  64. package/apps/web/providers/flag-provider.tsx +35 -0
  65. package/apps/web/providers/query-client-provider.tsx +17 -0
  66. package/apps/web/providers/telemetry-provider.tsx +12 -0
  67. package/apps/web/tsconfig.json +24 -0
  68. package/apps/web/utils/avatar.ts +26 -0
  69. package/apps/web/utils/date.ts +26 -0
  70. package/apps/web/utils/front-end-tracer.ts +119 -0
  71. package/apps/web/utils/schema/project.schema.ts +12 -0
  72. package/apps/web/utils/span-processor.ts +36 -0
  73. package/package.json +21 -0
  74. package/packages/eslint-config/README.md +3 -0
  75. package/packages/eslint-config/base.js +32 -0
  76. package/packages/eslint-config/next.js +51 -0
  77. package/packages/eslint-config/package.json +25 -0
  78. package/packages/eslint-config/react-internal.js +41 -0
  79. package/packages/rusty-replay/README.md +165 -0
  80. package/packages/rusty-replay/package.json +67 -0
  81. package/packages/rusty-replay/src/environment.ts +27 -0
  82. package/packages/rusty-replay/src/error-batcher.ts +75 -0
  83. package/packages/rusty-replay/src/front-end-tracer.ts +86 -0
  84. package/packages/rusty-replay/src/handler.ts +37 -0
  85. package/packages/rusty-replay/src/index.ts +8 -0
  86. package/packages/rusty-replay/src/recorder.ts +71 -0
  87. package/packages/rusty-replay/src/reporter.ts +115 -0
  88. package/packages/rusty-replay/src/utils.ts +13 -0
  89. package/packages/rusty-replay/tsconfig.build.json +13 -0
  90. package/packages/rusty-replay/tsconfig.json +27 -0
  91. package/packages/rusty-replay/tsup.config.ts +39 -0
  92. package/packages/typescript-config/README.md +3 -0
  93. package/packages/typescript-config/base.json +20 -0
  94. package/packages/typescript-config/nextjs.json +13 -0
  95. package/packages/typescript-config/package.json +9 -0
  96. package/packages/typescript-config/react-library.json +8 -0
  97. package/packages/ui/components.json +20 -0
  98. package/packages/ui/eslint.config.js +4 -0
  99. package/packages/ui/package.json +60 -0
  100. package/packages/ui/postcss.config.mjs +6 -0
  101. package/packages/ui/src/components/.gitkeep +0 -0
  102. package/packages/ui/src/components/avatar.tsx +53 -0
  103. package/packages/ui/src/components/badge.tsx +46 -0
  104. package/packages/ui/src/components/breadcrumb.tsx +109 -0
  105. package/packages/ui/src/components/button.tsx +59 -0
  106. package/packages/ui/src/components/calendar.tsx +75 -0
  107. package/packages/ui/src/components/calendars/date-picker.tsx +43 -0
  108. package/packages/ui/src/components/calendars/date-range-picker.tsx +79 -0
  109. package/packages/ui/src/components/card.tsx +92 -0
  110. package/packages/ui/src/components/checkbox.tsx +32 -0
  111. package/packages/ui/src/components/collapsible.tsx +33 -0
  112. package/packages/ui/src/components/dialog.tsx +135 -0
  113. package/packages/ui/src/components/dialogs/confirmation-modal.tsx +216 -0
  114. package/packages/ui/src/components/dropdown-menu.tsx +261 -0
  115. package/packages/ui/src/components/input.tsx +30 -0
  116. package/packages/ui/src/components/label.tsx +24 -0
  117. package/packages/ui/src/components/login-form.tsx +68 -0
  118. package/packages/ui/src/components/mode-switcher.tsx +34 -0
  119. package/packages/ui/src/components/popover.tsx +48 -0
  120. package/packages/ui/src/components/scroll-area.tsx +58 -0
  121. package/packages/ui/src/components/select.tsx +185 -0
  122. package/packages/ui/src/components/separator.tsx +28 -0
  123. package/packages/ui/src/components/sheet.tsx +139 -0
  124. package/packages/ui/src/components/sidebar.tsx +726 -0
  125. package/packages/ui/src/components/skeleton.tsx +13 -0
  126. package/packages/ui/src/components/sonner.tsx +25 -0
  127. package/packages/ui/src/components/table.tsx +116 -0
  128. package/packages/ui/src/components/tabs.tsx +66 -0
  129. package/packages/ui/src/components/team-switcher.tsx +91 -0
  130. package/packages/ui/src/components/textarea.tsx +18 -0
  131. package/packages/ui/src/components/tooltip.tsx +61 -0
  132. package/packages/ui/src/hooks/.gitkeep +0 -0
  133. package/packages/ui/src/hooks/use-meta-color.ts +28 -0
  134. package/packages/ui/src/hooks/use-mobile.ts +19 -0
  135. package/packages/ui/src/lib/utils.ts +6 -0
  136. package/packages/ui/src/styles/globals.css +138 -0
  137. package/packages/ui/tsconfig.json +13 -0
  138. package/packages/ui/tsconfig.lint.json +8 -0
  139. package/pnpm-workspace.yaml +4 -0
  140. package/tsconfig.json +4 -0
  141. package/turbo.json +21 -0
@@ -0,0 +1,176 @@
1
+ import { useState } from 'react';
2
+ import {
3
+ DropdownMenu,
4
+ DropdownMenuCheckboxItem,
5
+ DropdownMenuCheckboxItemProps,
6
+ DropdownMenuContent,
7
+ DropdownMenuLabel,
8
+ DropdownMenuSeparator,
9
+ DropdownMenuTrigger,
10
+ DropdownMenuItem,
11
+ } from '@workspace/ui/components/dropdown-menu';
12
+ import { ChevronDown, Search, Plus, UserCircle } from 'lucide-react';
13
+ import { Badge } from '@workspace/ui/components/badge';
14
+ import { toast } from '@workspace/ui/components/sonner';
15
+ import { useMutationEventAssignee } from '@/api/event/use-mutation-event-assignee';
16
+ import { ProjectMemberResponse } from '@/api/project/types';
17
+ import { Input } from '@workspace/ui/components/input';
18
+ import { getAvatarColor, getInitials } from '@/utils/avatar';
19
+
20
+ type Checked = DropdownMenuCheckboxItemProps['checked'];
21
+
22
+ interface Props {
23
+ projectId: number | undefined;
24
+ eventId: number;
25
+ userList: ProjectMemberResponse[] | undefined;
26
+ currentAssigneeId: number | null;
27
+ }
28
+
29
+ export function AssigneeDropdown({
30
+ projectId,
31
+ eventId,
32
+ userList,
33
+ currentAssigneeId,
34
+ }: Props) {
35
+ const [searchTerm, setSearchTerm] = useState<string>('');
36
+
37
+ const { mutateAsync: mutateAssign, isPending: isMutatingAssign } =
38
+ useMutationEventAssignee({
39
+ projectId: projectId!,
40
+ eventId,
41
+ });
42
+
43
+ const selectedUser = userList?.find(
44
+ (user) => user.userId === currentAssigneeId
45
+ );
46
+
47
+ const filteredUsers = userList?.filter(
48
+ (user) =>
49
+ user.username.toLowerCase().includes(searchTerm.toLowerCase()) ||
50
+ user.email.toLowerCase().includes(searchTerm.toLowerCase())
51
+ );
52
+
53
+ const handleAssigneeChange = async (userId: number) => {
54
+ try {
55
+ const isCurrentlySelected = currentAssigneeId === userId;
56
+ const newAssigneeId = isCurrentlySelected ? null : userId;
57
+
58
+ await mutateAssign(
59
+ { assignedTo: newAssigneeId, eventIds: [eventId] },
60
+ {
61
+ onSuccess: () => {
62
+ toast.success(
63
+ isCurrentlySelected
64
+ ? '담당자가 해제되었습니다!'
65
+ : '담당자가 지정되었습니다!'
66
+ );
67
+ },
68
+ onError: (error) => {
69
+ console.error('Error updating assignee:', error);
70
+ toast.error('담당자 변경에 실패했습니다.');
71
+ },
72
+ }
73
+ );
74
+ } catch (error) {
75
+ console.error('Error:', error);
76
+ }
77
+ };
78
+
79
+ return (
80
+ <DropdownMenu>
81
+ <DropdownMenuTrigger asChild>
82
+ <Badge
83
+ variant="secondary"
84
+ className="flex items-center gap-1 cursor-pointer"
85
+ >
86
+ {selectedUser ? (
87
+ <>
88
+ <div
89
+ className={`w-5 h-5 rounded-full flex items-center justify-center text-xs text-white ${getAvatarColor(selectedUser.username)}`}
90
+ >
91
+ {getInitials(selectedUser.username)}
92
+ </div>
93
+ {selectedUser.username}
94
+ </>
95
+ ) : (
96
+ <>
97
+ <UserCircle className="h-4 w-4" />
98
+ 담당자
99
+ </>
100
+ )}
101
+ <ChevronDown className="h-3 w-3 ml-1" />
102
+ </Badge>
103
+ </DropdownMenuTrigger>
104
+ <DropdownMenuContent className="w-72">
105
+ <DropdownMenuLabel className="font-bold text-lg">
106
+ 담당자
107
+ </DropdownMenuLabel>
108
+
109
+ <div className="px-2 py-2">
110
+ <div className="relative">
111
+ <Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
112
+ <Input
113
+ placeholder="사용자 또는 팀 검색..."
114
+ className="pl-8"
115
+ value={searchTerm}
116
+ onChange={(e) => setSearchTerm(e.target.value)}
117
+ />
118
+ </div>
119
+ </div>
120
+
121
+ <DropdownMenuSeparator />
122
+
123
+ <DropdownMenuLabel className="text-xs text-muted-foreground uppercase">
124
+ 멤버
125
+ </DropdownMenuLabel>
126
+
127
+ {filteredUsers?.map((user) => (
128
+ <DropdownMenuCheckboxItem
129
+ key={user.userId}
130
+ checked={currentAssigneeId === user.userId}
131
+ onCheckedChange={() => handleAssigneeChange(user.userId)}
132
+ className="flex items-center gap-2 py-2"
133
+ disabled={isMutatingAssign}
134
+ >
135
+ <div
136
+ className={`w-8 h-8 rounded-full flex items-center justify-center text-white text-sm ${getAvatarColor(user.username)}`}
137
+ >
138
+ {getInitials(user.username)}
139
+ </div>
140
+ <div className="flex flex-col">
141
+ <span className="font-medium">{user.username}</span>
142
+ <span className="text-xs text-muted-foreground">
143
+ {user.email}
144
+ </span>
145
+ </div>
146
+ </DropdownMenuCheckboxItem>
147
+ ))}
148
+
149
+ <DropdownMenuSeparator />
150
+
151
+ <DropdownMenuLabel className="text-xs text-muted-foreground uppercase">
152
+
153
+ </DropdownMenuLabel>
154
+
155
+ <DropdownMenuCheckboxItem
156
+ checked={false}
157
+ onCheckedChange={() => {}}
158
+ className="flex items-center gap-2 py-2"
159
+ disabled={true}
160
+ >
161
+ <div className="w-8 h-8 rounded-full flex items-center justify-center bg-pink-500 text-white text-sm">
162
+ R
163
+ </div>
164
+ <span className="font-medium">#reable</span>
165
+ </DropdownMenuCheckboxItem>
166
+
167
+ <DropdownMenuSeparator />
168
+
169
+ <DropdownMenuItem className="flex items-center gap-2 py-2">
170
+ <Plus className="h-4 w-4" />
171
+ <span>멤버 초대</span>
172
+ </DropdownMenuItem>
173
+ </DropdownMenuContent>
174
+ </DropdownMenu>
175
+ );
176
+ }
@@ -0,0 +1,104 @@
1
+ 'use client';
2
+ import { useState } from 'react';
3
+ import {
4
+ DropdownMenu,
5
+ DropdownMenuCheckboxItem,
6
+ DropdownMenuCheckboxItemProps,
7
+ DropdownMenuContent,
8
+ DropdownMenuLabel,
9
+ DropdownMenuSeparator,
10
+ DropdownMenuTrigger,
11
+ } from '@workspace/ui/components/dropdown-menu';
12
+ import { CheckCircle, XCircle, ChevronDown } from 'lucide-react';
13
+ import { Badge } from '@workspace/ui/components/badge';
14
+ import { EventStatusType } from '@/api/event/types';
15
+ import { useMutationEventStatus } from '@/api/event/use-mutation-event-status';
16
+ import { toast } from '@workspace/ui/components/sonner';
17
+
18
+ type Checked = DropdownMenuCheckboxItemProps['checked'];
19
+
20
+ interface Props {
21
+ status: EventStatusType | null;
22
+ projectId: number | undefined;
23
+ eventId: number;
24
+ }
25
+
26
+ const StatusIcon = ({ status }: { status: EventStatusType | null }) => {
27
+ switch (status) {
28
+ case 'RESOLVED':
29
+ return <CheckCircle className="h-4 w-4" />;
30
+ case 'UNRESOLVED':
31
+ return <XCircle className="h-4 w-4" />;
32
+ default:
33
+ return null;
34
+ }
35
+ };
36
+
37
+ export function EventStatusDropdown({ status, projectId, eventId }: Props) {
38
+ const [statusResolved, setStatusResolved] = useState<Checked>(
39
+ status === 'RESOLVED'
40
+ );
41
+ const [statusUnresolved, setStatusUnresolved] = useState<Checked>(
42
+ status === 'UNRESOLVED'
43
+ );
44
+
45
+ const { mutateAsync: mutateStatus, isPending: isMutatingStatus } =
46
+ useMutationEventStatus({ projectId: projectId! });
47
+
48
+ const handleStatusChange = async (status: EventStatusType) => {
49
+ await mutateStatus(
50
+ { status, eventIds: [eventId] },
51
+ {
52
+ onSuccess: () => {
53
+ setStatusResolved(status === 'RESOLVED');
54
+ setStatusUnresolved(status === 'UNRESOLVED');
55
+ toast.success('Status updated successfully!');
56
+ },
57
+ onError: (error) => {
58
+ console.error('Error updating status:', error);
59
+ toast.error('Failed to update status.');
60
+ },
61
+ }
62
+ );
63
+ };
64
+
65
+ return (
66
+ <DropdownMenu>
67
+ <DropdownMenuTrigger asChild>
68
+ <Badge
69
+ variant="secondary"
70
+ className="flex items-center gap-1 cursor-pointer"
71
+ >
72
+ <StatusIcon status={status} />
73
+ {status === 'RESOLVED' && 'Resolved'}
74
+ {status === 'UNRESOLVED' && 'Unresolved'}
75
+ <ChevronDown />
76
+ </Badge>
77
+ </DropdownMenuTrigger>
78
+ <DropdownMenuContent className="w-56">
79
+ <DropdownMenuLabel>Set Status</DropdownMenuLabel>
80
+ <DropdownMenuSeparator />
81
+ <DropdownMenuCheckboxItem
82
+ checked={statusResolved}
83
+ onCheckedChange={() => handleStatusChange('RESOLVED')}
84
+ className="flex items-center gap-2"
85
+ >
86
+ <Badge variant={'secondary'}>
87
+ <CheckCircle className="h-4 w-4" />
88
+ Resolved
89
+ </Badge>
90
+ </DropdownMenuCheckboxItem>
91
+ <DropdownMenuCheckboxItem
92
+ checked={statusUnresolved}
93
+ onCheckedChange={() => handleStatusChange('UNRESOLVED')}
94
+ className="flex items-center gap-2"
95
+ >
96
+ <Badge variant={'secondary'}>
97
+ <XCircle className="h-4 w-4" />
98
+ Unresolved
99
+ </Badge>
100
+ </DropdownMenuCheckboxItem>
101
+ </DropdownMenuContent>
102
+ </DropdownMenu>
103
+ );
104
+ }
@@ -0,0 +1,123 @@
1
+ 'use client';
2
+ import { useState } from 'react';
3
+ import {
4
+ DropdownMenu,
5
+ DropdownMenuCheckboxItem,
6
+ DropdownMenuCheckboxItemProps,
7
+ DropdownMenuContent,
8
+ DropdownMenuLabel,
9
+ DropdownMenuSeparator,
10
+ DropdownMenuTrigger,
11
+ } from '@workspace/ui/components/dropdown-menu';
12
+ import {
13
+ SignalHigh,
14
+ SignalMedium,
15
+ SignalLow,
16
+ ArrowDown,
17
+ ChevronDown,
18
+ } from 'lucide-react';
19
+ import { Badge } from '@workspace/ui/components/badge';
20
+ import { EventPriorityType } from '@/api/event/types';
21
+ import { useMutationEventPriority } from '@/api/event/use-mutation-event-priority';
22
+ import { toast } from '@workspace/ui/components/sonner';
23
+
24
+ type Checked = DropdownMenuCheckboxItemProps['checked'];
25
+
26
+ interface Props {
27
+ priority: EventPriorityType | null;
28
+ projectId: number | undefined;
29
+ eventId: number;
30
+ }
31
+
32
+ const PriorityIcon = ({ priority }: { priority: EventPriorityType | null }) => {
33
+ switch (priority) {
34
+ case 'HIGH':
35
+ return <SignalHigh className="h-4 w-4" />;
36
+ case 'MED':
37
+ return <SignalMedium className="h-4 w-4" />;
38
+ case 'LOW':
39
+ return <SignalLow className="h-4 w-4" />;
40
+ default:
41
+ return null;
42
+ }
43
+ };
44
+
45
+ export function PriorityDropdown({ priority, projectId, eventId }: Props) {
46
+ const [priorityHigh, setPriorityHigh] = useState<Checked>(
47
+ priority === 'HIGH'
48
+ );
49
+ const [priorityMed, setPriorityMed] = useState<Checked>(priority === 'MED');
50
+ const [priorityLow, setPriorityLow] = useState<Checked>(priority === 'LOW');
51
+
52
+ const { mutateAsync: mutatePriority, isPending: isMutatingPriority } =
53
+ useMutationEventPriority({ projectId: projectId! });
54
+
55
+ const handlePriorityChange = async (priority: EventPriorityType) => {
56
+ await mutatePriority(
57
+ { priority, eventIds: [eventId] },
58
+ {
59
+ onSuccess: () => {
60
+ setPriorityHigh(priority === 'HIGH');
61
+ setPriorityMed(priority === 'MED');
62
+ setPriorityLow(priority === 'LOW');
63
+ toast.success('Priority updated successfully!');
64
+ },
65
+ onError: (error) => {
66
+ console.error('Error updating priority:', error);
67
+ toast.error('Failed to update priority.');
68
+ },
69
+ }
70
+ );
71
+ };
72
+
73
+ return (
74
+ <DropdownMenu>
75
+ <DropdownMenuTrigger asChild>
76
+ <Badge
77
+ variant="secondary"
78
+ className="flex items-center gap-1 cursor-pointer"
79
+ >
80
+ <PriorityIcon priority={priority} />
81
+ {priority === 'HIGH' && 'High'}
82
+ {priority === 'MED' && 'Med'}
83
+ {priority === 'LOW' && 'Low'}
84
+ <ChevronDown />
85
+ </Badge>
86
+ </DropdownMenuTrigger>
87
+ <DropdownMenuContent className="w-56">
88
+ <DropdownMenuLabel>Set Priority</DropdownMenuLabel>
89
+ <DropdownMenuSeparator />
90
+ <DropdownMenuCheckboxItem
91
+ checked={priorityHigh}
92
+ onCheckedChange={() => handlePriorityChange('HIGH')}
93
+ className="flex items-center gap-2"
94
+ >
95
+ <Badge variant={'secondary'}>
96
+ <SignalHigh className="h-4 w-4" />
97
+ High
98
+ </Badge>
99
+ </DropdownMenuCheckboxItem>
100
+ <DropdownMenuCheckboxItem
101
+ checked={priorityMed}
102
+ onCheckedChange={() => handlePriorityChange('MED')}
103
+ className="flex items-center gap-2"
104
+ >
105
+ <Badge variant={'secondary'}>
106
+ <SignalMedium className="h-4 w-4" />
107
+ Med
108
+ </Badge>
109
+ </DropdownMenuCheckboxItem>
110
+ <DropdownMenuCheckboxItem
111
+ checked={priorityLow}
112
+ onCheckedChange={() => handlePriorityChange('LOW')}
113
+ className="flex items-center gap-2"
114
+ >
115
+ <Badge variant={'secondary'}>
116
+ <SignalLow className="h-4 w-4" />
117
+ Low
118
+ </Badge>
119
+ </DropdownMenuCheckboxItem>
120
+ </DropdownMenuContent>
121
+ </DropdownMenu>
122
+ );
123
+ }
@@ -0,0 +1,225 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import {
5
+ Activity,
6
+ AlertCircle,
7
+ BarChart3,
8
+ Bell,
9
+ Bug,
10
+ Clock,
11
+ Code,
12
+ Command,
13
+ Cpu,
14
+ Gauge,
15
+ LineChart,
16
+ List,
17
+ Settings2,
18
+ Shield,
19
+ Users2,
20
+ Zap,
21
+ } from 'lucide-react';
22
+
23
+ import { TeamSwitcher } from '@workspace/ui/components/team-switcher';
24
+ import {
25
+ Sidebar,
26
+ SidebarContent,
27
+ SidebarFooter,
28
+ SidebarHeader,
29
+ SidebarRail,
30
+ } from '@workspace/ui/components/sidebar';
31
+ import { NavProjects } from './nav-projects';
32
+ import { NavMain } from './nav-main';
33
+ import { useQueryProfile } from '@/api/auth/use-query-profile';
34
+ import { NavUser } from './nav-user';
35
+
36
+ const data = {
37
+ user: {
38
+ name: 'shadcn',
39
+ email: 'm@example.com',
40
+ avatar: '/avatars/shadcn.jpg',
41
+ },
42
+ teams: [
43
+ {
44
+ name: 'Frontend Team',
45
+ logo: Code,
46
+ plan: 'Enterprise',
47
+ },
48
+ {
49
+ name: 'Backend Team',
50
+ logo: Command,
51
+ plan: 'Enterprise',
52
+ },
53
+ {
54
+ name: 'DevOps Team',
55
+ logo: Cpu,
56
+ plan: 'Free',
57
+ },
58
+ ],
59
+ projects: [
60
+ {
61
+ name: 'Projects',
62
+ url: '/project',
63
+ icon: Activity,
64
+ },
65
+ {
66
+ name: 'Issues',
67
+ url: '#',
68
+ icon: Shield,
69
+ },
70
+ ],
71
+ navMain: [
72
+ {
73
+ title: '대시보드',
74
+ url: '#',
75
+ icon: Gauge,
76
+ isActive: true,
77
+ items: [
78
+ {
79
+ title: '실시간 모니터링',
80
+ url: '#',
81
+ },
82
+ {
83
+ title: '서비스 상태',
84
+ url: '#',
85
+ },
86
+ {
87
+ title: '성능 지표',
88
+ url: '#',
89
+ },
90
+ ],
91
+ },
92
+ {
93
+ title: '이슈 트래킹',
94
+ url: '#',
95
+ icon: Bug,
96
+ items: [
97
+ {
98
+ title: '모든 이슈',
99
+ url: '#',
100
+ },
101
+ {
102
+ title: '해결되지 않은 이슈',
103
+ url: '#',
104
+ },
105
+ {
106
+ title: '중요 이슈',
107
+ url: '#',
108
+ },
109
+ {
110
+ title: '나에게 할당된 이슈',
111
+ url: '#',
112
+ },
113
+ ],
114
+ },
115
+ {
116
+ title: '성능',
117
+ url: '#',
118
+ icon: Zap,
119
+ items: [
120
+ {
121
+ title: '트랜잭션',
122
+ url: '#',
123
+ },
124
+ {
125
+ title: '웹 성능',
126
+ url: '#',
127
+ },
128
+ {
129
+ title: 'API 성능',
130
+ url: '#',
131
+ },
132
+ {
133
+ title: '백엔드 성능',
134
+ url: '#',
135
+ },
136
+ ],
137
+ },
138
+ {
139
+ title: '분석',
140
+ url: '#',
141
+ icon: LineChart,
142
+ items: [
143
+ {
144
+ title: '트래픽 분석',
145
+ url: '#',
146
+ },
147
+ {
148
+ title: '에러 발생률',
149
+ url: '#',
150
+ },
151
+ {
152
+ title: '사용자 세션',
153
+ url: '#',
154
+ },
155
+ {
156
+ title: '커스텀 리포트',
157
+ url: '#',
158
+ },
159
+ ],
160
+ },
161
+ {
162
+ title: '알림',
163
+ url: '#',
164
+ icon: Bell,
165
+ items: [
166
+ {
167
+ title: '알림 설정',
168
+ url: '#',
169
+ },
170
+ {
171
+ title: '알림 이력',
172
+ url: '#',
173
+ },
174
+ {
175
+ title: '알림 룰',
176
+ url: '#',
177
+ },
178
+ ],
179
+ },
180
+ {
181
+ title: '설정',
182
+ url: '#',
183
+ icon: Settings2,
184
+ items: [
185
+ {
186
+ title: '프로젝트 설정',
187
+ url: '#',
188
+ },
189
+ {
190
+ title: '팀 관리',
191
+ url: '#',
192
+ },
193
+ {
194
+ title: 'SDK 설정',
195
+ url: '#',
196
+ },
197
+ {
198
+ title: '통합 설정',
199
+ url: '#',
200
+ },
201
+ ],
202
+ },
203
+ ],
204
+ };
205
+
206
+ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
207
+ const { data: profile, isPending: isPendingProfile } = useQueryProfile();
208
+
209
+ return (
210
+ <Sidebar collapsible="icon" {...props}>
211
+ <SidebarHeader>
212
+ <TeamSwitcher teams={data.teams} />
213
+ </SidebarHeader>
214
+ <SidebarContent>
215
+ <NavProjects projects={data.projects} />
216
+
217
+ <NavMain items={data.navMain} />
218
+ </SidebarContent>
219
+ <SidebarFooter>
220
+ <NavUser profile={profile} />
221
+ </SidebarFooter>
222
+ <SidebarRail />
223
+ </Sidebar>
224
+ );
225
+ }
@@ -0,0 +1,73 @@
1
+ 'use client';
2
+
3
+ import { ChevronRight, type LucideIcon } from 'lucide-react';
4
+
5
+ import {
6
+ Collapsible,
7
+ CollapsibleContent,
8
+ CollapsibleTrigger,
9
+ } from '@workspace/ui/components/collapsible';
10
+ import {
11
+ SidebarGroup,
12
+ SidebarGroupLabel,
13
+ SidebarMenu,
14
+ SidebarMenuButton,
15
+ SidebarMenuItem,
16
+ SidebarMenuSub,
17
+ SidebarMenuSubButton,
18
+ SidebarMenuSubItem,
19
+ } from '@workspace/ui/components/sidebar';
20
+
21
+ export function NavMain({
22
+ items,
23
+ }: {
24
+ items: {
25
+ title: string;
26
+ url: string;
27
+ icon?: LucideIcon;
28
+ isActive?: boolean;
29
+ items?: {
30
+ title: string;
31
+ url: string;
32
+ }[];
33
+ }[];
34
+ }) {
35
+ return (
36
+ <SidebarGroup>
37
+ <SidebarGroupLabel>Platform</SidebarGroupLabel>
38
+ <SidebarMenu>
39
+ {items.map((item) => (
40
+ <Collapsible
41
+ key={item.title}
42
+ asChild
43
+ defaultOpen={item.isActive}
44
+ className="group/collapsible"
45
+ >
46
+ <SidebarMenuItem>
47
+ <CollapsibleTrigger asChild>
48
+ <SidebarMenuButton tooltip={item.title}>
49
+ {item.icon && <item.icon />}
50
+ <span>{item.title}</span>
51
+ <ChevronRight className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
52
+ </SidebarMenuButton>
53
+ </CollapsibleTrigger>
54
+ <CollapsibleContent>
55
+ <SidebarMenuSub>
56
+ {item.items?.map((subItem) => (
57
+ <SidebarMenuSubItem key={subItem.title}>
58
+ <SidebarMenuSubButton asChild>
59
+ <a href={subItem.url}>
60
+ <span>{subItem.title}</span>
61
+ </a>
62
+ </SidebarMenuSubButton>
63
+ </SidebarMenuSubItem>
64
+ ))}
65
+ </SidebarMenuSub>
66
+ </CollapsibleContent>
67
+ </SidebarMenuItem>
68
+ </Collapsible>
69
+ ))}
70
+ </SidebarMenu>
71
+ </SidebarGroup>
72
+ );
73
+ }