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,84 @@
1
+ 'use client';
2
+
3
+ import {
4
+ Folder,
5
+ Forward,
6
+ MoreHorizontal,
7
+ Trash2,
8
+ type LucideIcon,
9
+ } from 'lucide-react';
10
+
11
+ import {
12
+ DropdownMenu,
13
+ DropdownMenuContent,
14
+ DropdownMenuItem,
15
+ DropdownMenuSeparator,
16
+ DropdownMenuTrigger,
17
+ } from '@workspace/ui/components/dropdown-menu';
18
+ import {
19
+ SidebarGroup,
20
+ SidebarGroupLabel,
21
+ SidebarMenu,
22
+ SidebarMenuAction,
23
+ SidebarMenuButton,
24
+ SidebarMenuItem,
25
+ useSidebar,
26
+ } from '@workspace/ui/components/sidebar';
27
+ import Link from 'next/link';
28
+
29
+ export function NavProjects({
30
+ projects,
31
+ }: {
32
+ projects: {
33
+ name: string;
34
+ url: string;
35
+ icon: LucideIcon;
36
+ }[];
37
+ }) {
38
+ const { isMobile } = useSidebar();
39
+
40
+ return (
41
+ <SidebarGroup className="group-data-[collapsible=icon]:hidden">
42
+ <SidebarGroupLabel>Projects</SidebarGroupLabel>
43
+ <SidebarMenu>
44
+ {projects.map((item) => (
45
+ <SidebarMenuItem key={item.name}>
46
+ <SidebarMenuButton asChild>
47
+ <Link href={item.url}>
48
+ <item.icon />
49
+ <span>{item.name}</span>
50
+ </Link>
51
+ </SidebarMenuButton>
52
+ <DropdownMenu>
53
+ <DropdownMenuTrigger asChild>
54
+ <SidebarMenuAction showOnHover>
55
+ <MoreHorizontal />
56
+ <span className="sr-only">More</span>
57
+ </SidebarMenuAction>
58
+ </DropdownMenuTrigger>
59
+ <DropdownMenuContent
60
+ className="w-48 rounded-lg"
61
+ side={isMobile ? 'bottom' : 'right'}
62
+ align={isMobile ? 'end' : 'start'}
63
+ >
64
+ <DropdownMenuItem>
65
+ <Folder className="text-muted-foreground" />
66
+ <span>View Project</span>
67
+ </DropdownMenuItem>
68
+ <DropdownMenuItem>
69
+ <Forward className="text-muted-foreground" />
70
+ <span>Share Project</span>
71
+ </DropdownMenuItem>
72
+ <DropdownMenuSeparator />
73
+ <DropdownMenuItem>
74
+ <Trash2 className="text-muted-foreground" />
75
+ <span>Delete Project</span>
76
+ </DropdownMenuItem>
77
+ </DropdownMenuContent>
78
+ </DropdownMenu>
79
+ </SidebarMenuItem>
80
+ ))}
81
+ </SidebarMenu>
82
+ </SidebarGroup>
83
+ );
84
+ }
@@ -0,0 +1,113 @@
1
+ 'use client';
2
+
3
+ import {
4
+ BadgeCheck,
5
+ Bell,
6
+ ChevronsUpDown,
7
+ CreditCard,
8
+ LogOut,
9
+ Sparkles,
10
+ } from 'lucide-react';
11
+
12
+ import {
13
+ Avatar,
14
+ AvatarFallback,
15
+ AvatarImage,
16
+ } from '@workspace/ui/components/avatar';
17
+ import {
18
+ DropdownMenu,
19
+ DropdownMenuContent,
20
+ DropdownMenuGroup,
21
+ DropdownMenuItem,
22
+ DropdownMenuLabel,
23
+ DropdownMenuSeparator,
24
+ DropdownMenuTrigger,
25
+ } from '@workspace/ui/components/dropdown-menu';
26
+ import {
27
+ SidebarMenu,
28
+ SidebarMenuButton,
29
+ SidebarMenuItem,
30
+ useSidebar,
31
+ } from '@workspace/ui/components/sidebar';
32
+ import { UserResponse } from '@/api/auth/types';
33
+
34
+ export function NavUser({ profile }: { profile: UserResponse | undefined }) {
35
+ const { isMobile } = useSidebar();
36
+
37
+ return (
38
+ <SidebarMenu>
39
+ <SidebarMenuItem>
40
+ <DropdownMenu>
41
+ <DropdownMenuTrigger asChild>
42
+ <SidebarMenuButton
43
+ size="lg"
44
+ className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
45
+ >
46
+ <Avatar className="h-8 w-8 rounded-lg">
47
+ {/* <AvatarImage src={profile?.} alt={profile?.username} /> */}
48
+ <AvatarFallback className="rounded-lg">
49
+ {profile?.username.substring(0, 2)}
50
+ </AvatarFallback>
51
+ </Avatar>
52
+ <div className="grid flex-1 text-left text-sm leading-tight">
53
+ <span className="truncate font-medium">
54
+ {profile?.username}
55
+ </span>
56
+ <span className="truncate text-xs">{profile?.email}</span>
57
+ </div>
58
+ <ChevronsUpDown className="ml-auto size-4" />
59
+ </SidebarMenuButton>
60
+ </DropdownMenuTrigger>
61
+ <DropdownMenuContent
62
+ className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
63
+ side={isMobile ? 'bottom' : 'right'}
64
+ align="end"
65
+ sideOffset={4}
66
+ >
67
+ <DropdownMenuLabel className="p-0 font-normal">
68
+ <div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
69
+ <Avatar className="h-8 w-8 rounded-lg">
70
+ {/* <AvatarImage src={profile.avatar} alt={profile?.username} /> */}
71
+ <AvatarFallback className="rounded-lg">CN</AvatarFallback>
72
+ </Avatar>
73
+ <div className="grid flex-1 text-left text-sm leading-tight">
74
+ <span className="truncate font-medium">
75
+ {profile?.username}
76
+ </span>
77
+ <span className="truncate text-xs">{profile?.email}</span>
78
+ </div>
79
+ </div>
80
+ </DropdownMenuLabel>
81
+ <DropdownMenuSeparator />
82
+ <DropdownMenuGroup>
83
+ <DropdownMenuItem>
84
+ <Sparkles />
85
+ Upgrade to Pro
86
+ </DropdownMenuItem>
87
+ </DropdownMenuGroup>
88
+ <DropdownMenuSeparator />
89
+ <DropdownMenuGroup>
90
+ <DropdownMenuItem>
91
+ <BadgeCheck />
92
+ Account
93
+ </DropdownMenuItem>
94
+ <DropdownMenuItem>
95
+ <CreditCard />
96
+ Billing
97
+ </DropdownMenuItem>
98
+ <DropdownMenuItem>
99
+ <Bell />
100
+ Notifications
101
+ </DropdownMenuItem>
102
+ </DropdownMenuGroup>
103
+ <DropdownMenuSeparator />
104
+ <DropdownMenuItem>
105
+ <LogOut />
106
+ Log out
107
+ </DropdownMenuItem>
108
+ </DropdownMenuContent>
109
+ </DropdownMenu>
110
+ </SidebarMenuItem>
111
+ </SidebarMenu>
112
+ );
113
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "new-york",
4
+ "rsc": true,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "../../packages/ui/src/styles/globals.css",
9
+ "baseColor": "neutral",
10
+ "cssVariables": true
11
+ },
12
+ "iconLibrary": "lucide",
13
+ "aliases": {
14
+ "components": "@/components",
15
+ "hooks": "@/hooks",
16
+ "lib": "@/lib",
17
+ "utils": "@workspace/ui/lib/utils",
18
+ "ui": "@workspace/ui/components"
19
+ }
20
+ }
@@ -0,0 +1,12 @@
1
+ export const routes = {
2
+ home: () => `/`,
3
+ event: {
4
+ detail: (projectId: number, issueId: number) =>
5
+ `/project/${projectId}/issues/${issueId}`,
6
+ list: (projectId: number) => `/project/${projectId}/issues`,
7
+ },
8
+ project: {
9
+ list: () => `/projects`,
10
+ detail: (projectId: number) => `/project/${projectId}`,
11
+ },
12
+ };
@@ -0,0 +1,4 @@
1
+ import { nextJsConfig } from "@workspace/eslint-config/next-js"
2
+
3
+ /** @type {import("eslint").Linter.Config} */
4
+ export default nextJsConfig
@@ -0,0 +1,13 @@
1
+ import { useState, useCallback } from 'react';
2
+
3
+ export function useBooleanState() {
4
+ const [isOpen, setIsOpen] = useState<boolean>(false);
5
+
6
+ const open = useCallback(() => setIsOpen(true), [setIsOpen]);
7
+
8
+ const close = useCallback(() => setIsOpen(false), [setIsOpen]);
9
+
10
+ const toggle = useCallback(() => setIsOpen((prev) => !prev), [setIsOpen]);
11
+
12
+ return { isOpen, open, close, toggle };
13
+ }
File without changes
@@ -0,0 +1,5 @@
1
+ /// <reference types="next" />
2
+ /// <reference types="next/image-types/global" />
3
+
4
+ // NOTE: This file should not be edited
5
+ // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
@@ -0,0 +1,6 @@
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {
3
+ transpilePackages: ["@workspace/ui"],
4
+ }
5
+
6
+ export default nextConfig
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "web",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "private": true,
6
+ "scripts": {
7
+ "dev": "next dev --turbopack",
8
+ "build": "next build",
9
+ "start": "next start",
10
+ "lint": "next lint",
11
+ "lint:fix": "next lint --fix",
12
+ "typecheck": "tsc --noEmit"
13
+ },
14
+ "dependencies": {
15
+ "@hookform/resolvers": "^5.0.1",
16
+ "@openfeature/flagd-web-provider": "^0.7.3",
17
+ "@openfeature/react-sdk": "^1.0.0",
18
+ "@opentelemetry/auto-instrumentations-web": "^0.46.0",
19
+ "@opentelemetry/context-zone": "^2.0.0",
20
+ "@opentelemetry/core": "^2.0.0",
21
+ "@opentelemetry/exporter-trace-otlp-http": "^0.200.0",
22
+ "@opentelemetry/exporter-trace-otlp-proto": "^0.200.0",
23
+ "@opentelemetry/instrumentation": "^0.200.0",
24
+ "@opentelemetry/instrumentation-fetch": "^0.200.0",
25
+ "@opentelemetry/instrumentation-xml-http-request": "^0.200.0",
26
+ "@opentelemetry/resources": "^2.0.0",
27
+ "@opentelemetry/sdk-trace-base": "^2.0.0",
28
+ "@opentelemetry/sdk-trace-web": "^2.0.0",
29
+ "@opentelemetry/semantic-conventions": "^1.32.0",
30
+ "@rrweb/packer": "2.0.0-alpha.18",
31
+ "@tanstack/react-query": "^5.74.4",
32
+ "@workspace/ui": "workspace:*",
33
+ "axios": "^1.8.4",
34
+ "dayjs": "^1.11.13",
35
+ "framer-motion": "^12.9.2",
36
+ "import-in-the-middle": "^1.13.1",
37
+ "lucide-react": "^0.475.0",
38
+ "next": "^15.2.3",
39
+ "next-themes": "^0.4.4",
40
+ "react": "^19.0.0",
41
+ "react-day-picker": "8.10.1",
42
+ "react-dom": "^19.0.0",
43
+ "react-hook-form": "^7.56.1",
44
+ "require-in-the-middle": "^7.5.2",
45
+ "rrweb": "2.0.0-alpha.4",
46
+ "rrweb-player": "1.0.0-alpha.4",
47
+ "rusty-replay": "workspace:*",
48
+ "zod": "^3.24.2"
49
+ },
50
+ "devDependencies": {
51
+ "@rrweb/types": "2.0.0-alpha.18",
52
+ "@tanstack/eslint-plugin-query": "^5.73.3",
53
+ "@types/node": "^20",
54
+ "@types/react": "^19",
55
+ "@types/react-dom": "^19",
56
+ "@workspace/eslint-config": "workspace:^",
57
+ "@workspace/typescript-config": "workspace:*",
58
+ "typescript": "^5.7.3"
59
+ }
60
+ }
@@ -0,0 +1 @@
1
+ export { default } from "@workspace/ui/postcss.config";
@@ -0,0 +1,35 @@
1
+ 'use client';
2
+
3
+ import React, { PropsWithChildren, useEffect } from 'react';
4
+ import { OpenFeature } from '@openfeature/react-sdk';
5
+ import { FlagdWebProvider } from '@openfeature/flagd-web-provider';
6
+
7
+ export default function FlagProvider({ children }: PropsWithChildren) {
8
+ useEffect(() => {
9
+ // const session = SessionGateway.getSession();
10
+ // OpenFeature.setContext({ targetingKey: session.userId, ...session });
11
+ OpenFeature.setContext({ targetingKey: '1' })
12
+ .then(() => {
13
+ const useTLS = window.location.protocol === 'https:';
14
+ const port = window.location.port
15
+ ? parseInt(window.location.port, 10)
16
+ : useTLS
17
+ ? 443
18
+ : 80;
19
+
20
+ OpenFeature.setProvider(
21
+ new FlagdWebProvider({
22
+ host: window.location.hostname,
23
+ pathPrefix: 'flagservice',
24
+ port,
25
+ tls: useTLS,
26
+ maxRetries: 3,
27
+ maxDelay: 10000,
28
+ })
29
+ );
30
+ })
31
+ .catch(console.error);
32
+ }, []);
33
+
34
+ return <>{children}</>;
35
+ }
@@ -0,0 +1,17 @@
1
+ 'use client';
2
+
3
+ import React, { PropsWithChildren, useEffect } from 'react';
4
+ import {
5
+ QueryClient,
6
+ QueryClientProvider as ReactQueryClientProvider,
7
+ } from '@tanstack/react-query';
8
+
9
+ const queryClient = new QueryClient({});
10
+
11
+ export default function QueryClientProvider({ children }: PropsWithChildren) {
12
+ return (
13
+ <ReactQueryClientProvider client={queryClient}>
14
+ {children}
15
+ </ReactQueryClientProvider>
16
+ );
17
+ }
@@ -0,0 +1,12 @@
1
+ 'use client';
2
+
3
+ import { FrontendTracer } from '@/utils/front-end-tracer';
4
+ import React, { PropsWithChildren, useEffect } from 'react';
5
+
6
+ export default function TelemetryProvider({ children }: PropsWithChildren) {
7
+ useEffect(() => {
8
+ FrontendTracer();
9
+ }, []);
10
+
11
+ return <>{children}</>;
12
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "extends": "@workspace/typescript-config/nextjs.json",
3
+ "compilerOptions": {
4
+ "baseUrl": ".",
5
+ "paths": {
6
+ "@/*": ["./*"],
7
+ "@workspace/ui/*": ["../../packages/ui/src/*"],
8
+ "@workspace/rusty-replay/*": ["../../packages/rusty-replay/src/*"]
9
+ },
10
+ "plugins": [
11
+ {
12
+ "name": "next"
13
+ }
14
+ ]
15
+ },
16
+ "include": [
17
+ "next-env.d.ts",
18
+ "next.config.ts",
19
+ "**/*.ts",
20
+ "**/*.tsx",
21
+ ".next/types/**/*.ts"
22
+ ],
23
+ "exclude": ["node_modules"]
24
+ }
@@ -0,0 +1,26 @@
1
+ export function getInitials(name: string) {
2
+ return name
3
+ .split(' ')
4
+ .map((word) => word[0])
5
+ .join('')
6
+ .toUpperCase()
7
+ .slice(0, 2);
8
+ }
9
+
10
+ export function getAvatarColor(name: string) {
11
+ const colors = [
12
+ 'bg-blue-500',
13
+ 'bg-purple-500',
14
+ 'bg-orange-500',
15
+ 'bg-green-500',
16
+ 'bg-indigo-500',
17
+ 'bg-teal-500',
18
+ 'bg-pink-500',
19
+ ];
20
+
21
+ let hash = 0;
22
+ for (let i = 0; i < name.length; i++) {
23
+ hash = name.charCodeAt(i) + ((hash << 5) - hash);
24
+ }
25
+ return colors[Math.abs(hash) % colors.length];
26
+ }
@@ -0,0 +1,26 @@
1
+ import dayjs from 'dayjs';
2
+
3
+ export function formatDateFromNow(dateString: string | null) {
4
+ if (!dateString) {
5
+ return '';
6
+ }
7
+
8
+ try {
9
+ return dayjs(dateString).fromNow();
10
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
11
+ } catch (e) {
12
+ return dateString;
13
+ }
14
+ }
15
+
16
+ export function formatDate(
17
+ dateString: string,
18
+ format: string = 'YYYY-MM-DD HH:mm:ss'
19
+ ) {
20
+ try {
21
+ return dayjs(dateString).format(format);
22
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
23
+ } catch (e) {
24
+ return dateString;
25
+ }
26
+ }
@@ -0,0 +1,119 @@
1
+ import {
2
+ CompositePropagator,
3
+ W3CBaggagePropagator,
4
+ W3CTraceContextPropagator,
5
+ } from '@opentelemetry/core';
6
+ import { WebTracerProvider } from '@opentelemetry/sdk-trace-web';
7
+ import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
8
+ import { registerInstrumentations } from '@opentelemetry/instrumentation';
9
+ import { getWebAutoInstrumentations } from '@opentelemetry/auto-instrumentations-web';
10
+ import { resourceFromAttributes, osDetector } from '@opentelemetry/resources';
11
+ import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
12
+ // import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
13
+ import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
14
+ // import { SessionIdProcessor } from './SessionIdProcessor';
15
+ import { detectResources } from '@opentelemetry/resources/build/src/detect-resources';
16
+ import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch';
17
+ import { XMLHttpRequestInstrumentation } from '@opentelemetry/instrumentation-xml-http-request';
18
+
19
+ declare global {
20
+ interface Window {
21
+ ENV: {
22
+ NEXT_PUBLIC_PLATFORM?: string;
23
+ NEXT_PUBLIC_OTEL_SERVICE_NAME?: string;
24
+ NEXT_PUBLIC_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT?: string;
25
+ IS_SYNTHETIC_REQUEST?: string;
26
+ };
27
+ }
28
+ }
29
+
30
+ const {
31
+ NEXT_PUBLIC_OTEL_SERVICE_NAME = 'replay',
32
+ NEXT_PUBLIC_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = '',
33
+ IS_SYNTHETIC_REQUEST = '',
34
+ } = typeof window !== 'undefined' && window.ENV
35
+ ? window.ENV
36
+ : {
37
+ NEXT_PUBLIC_OTEL_SERVICE_NAME:
38
+ process.env.NEXT_PUBLIC_OTEL_SERVICE_NAME || 'replay',
39
+ NEXT_PUBLIC_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT:
40
+ process.env.NEXT_PUBLIC_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT || '',
41
+ IS_SYNTHETIC_REQUEST: process.env.IS_SYNTHETIC_REQUEST || '',
42
+ };
43
+
44
+ const endpoint =
45
+ NEXT_PUBLIC_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||
46
+ 'http://localhost:8081/traces';
47
+
48
+ export const FrontendTracer = async () => {
49
+ const { ZoneContextManager } = await import('@opentelemetry/context-zone');
50
+
51
+ let resource = resourceFromAttributes({
52
+ [ATTR_SERVICE_NAME]: NEXT_PUBLIC_OTEL_SERVICE_NAME,
53
+ });
54
+ const detectedResources = detectResources({ detectors: [osDetector] });
55
+ resource = resource.merge(detectedResources);
56
+
57
+ const provider = new WebTracerProvider({
58
+ resource,
59
+ spanProcessors: [
60
+ // new SessionIdProcessor(),
61
+ new BatchSpanProcessor(
62
+ new OTLPTraceExporter({
63
+ url:
64
+ NEXT_PUBLIC_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||
65
+ 'http://localhost:8081/traces',
66
+
67
+ headers: {
68
+ 'Content-Type': 'application/x-protobuf',
69
+ },
70
+ }),
71
+ {
72
+ scheduledDelayMillis: 500,
73
+ }
74
+ ),
75
+ ],
76
+ });
77
+
78
+ const contextManager = new ZoneContextManager();
79
+
80
+ provider.register({
81
+ contextManager,
82
+ // fetch, xhr을 제외한 모든 event
83
+ // propagator: new CompositePropagator({
84
+ // propagators: [
85
+ // new W3CBaggagePropagator(),
86
+ // new W3CTraceContextPropagator(),
87
+ // ],
88
+ // }),
89
+ });
90
+
91
+ const backendOrigin = new URL(endpoint).origin; // "http://localhost:8081"
92
+
93
+ const fetchInstrumentation = new FetchInstrumentation({
94
+ propagateTraceHeaderCorsUrls: [backendOrigin],
95
+ clearTimingResources: true,
96
+ });
97
+
98
+ const xhrInstrumentation = new XMLHttpRequestInstrumentation({
99
+ propagateTraceHeaderCorsUrls: [backendOrigin],
100
+ clearTimingResources: true,
101
+ });
102
+
103
+ registerInstrumentations({
104
+ tracerProvider: provider,
105
+ instrumentations: [
106
+ fetchInstrumentation,
107
+ xhrInstrumentation,
108
+ // getWebAutoInstrumentations({
109
+ // '@opentelemetry/instrumentation-fetch': {
110
+ // propagateTraceHeaderCorsUrls: /.*/,
111
+ // clearTimingResources: true,
112
+ // applyCustomAttributesOnSpan(span) {
113
+ // span.setAttribute('app.synthetic_request', IS_SYNTHETIC_REQUEST);
114
+ // },
115
+ // },
116
+ // }),
117
+ ],
118
+ });
119
+ };
@@ -0,0 +1,12 @@
1
+ import { z } from 'zod';
2
+
3
+ export const CreateProjectSchema = z.object({
4
+ name: z
5
+ .string({
6
+ required_error: '프로젝트 이름을 입력하세요',
7
+ })
8
+ .min(1, { message: '프로젝트 이름을 입력하세요' }),
9
+ description: z.string().optional().nullable(),
10
+ });
11
+
12
+ export type CreateProjectSchemaType = z.infer<typeof CreateProjectSchema>;
@@ -0,0 +1,36 @@
1
+ import {
2
+ SimpleSpanProcessor,
3
+ ReadableSpan,
4
+ SpanProcessor,
5
+ } from '@opentelemetry/sdk-trace-base';
6
+ import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
7
+
8
+ class EndpointOnlySpanProcessor implements SpanProcessor {
9
+ private delegate = new SimpleSpanProcessor(
10
+ new OTLPTraceExporter({
11
+ url: process.env.NEXT_PUBLIC_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
12
+ headers: { 'Content-Type': 'application/x-protobuf' },
13
+ })
14
+ );
15
+
16
+ onStart(_span: ReadableSpan) {}
17
+
18
+ onEnd(span: ReadableSpan) {
19
+ const url = span.attributes['http.url'] as string | undefined;
20
+ if (
21
+ url &&
22
+ url.startsWith(
23
+ process.env.NEXT_PUBLIC_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT!
24
+ )
25
+ ) {
26
+ this.delegate.onEnd(span);
27
+ }
28
+ }
29
+
30
+ shutdown() {
31
+ return this.delegate.shutdown();
32
+ }
33
+ forceFlush() {
34
+ return this.delegate.forceFlush();
35
+ }
36
+ }
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "rusty-replay",
3
+ "version": "0.0.4",
4
+ "scripts": {
5
+ "build": "turbo build",
6
+ "dev": "turbo dev",
7
+ "lint": "turbo lint",
8
+ "format": "prettier --write \"**/*.{ts,tsx,md}\""
9
+ },
10
+ "devDependencies": {
11
+ "@workspace/eslint-config": "workspace:*",
12
+ "@workspace/typescript-config": "workspace:*",
13
+ "prettier": "^3.5.1",
14
+ "turbo": "^2.4.2",
15
+ "typescript": "5.7.3"
16
+ },
17
+ "packageManager": "pnpm@10.4.1",
18
+ "engines": {
19
+ "node": ">=20"
20
+ }
21
+ }
@@ -0,0 +1,3 @@
1
+ # `@workspace/eslint-config`
2
+
3
+ Shared eslint configuration for the workspace.