create-aron-app 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -2
- package/templates/_base/.cursor/agents/skills/clerk/SKILL.md +89 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/SKILL.md +142 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/scripts/api-specs-context.sh +30 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/scripts/execute-request.sh +88 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/scripts/extract-endpoint-detail.sh +165 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/scripts/extract-tag-endpoints.sh +208 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/scripts/extract-tags.js +14 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/SKILL.md +157 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/core-2/custom-sign-in.md +224 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/core-2/custom-sign-up.md +190 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/core-3/custom-sign-in.md +314 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/core-3/custom-sign-up.md +259 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/core-3/show-component.md +125 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/SKILL.md +94 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/references/api-routes.md +50 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/references/caching-auth.md +56 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/references/middleware-strategies.md +68 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/references/server-actions.md +56 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/references/server-vs-client.md +104 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-webhooks/SKILL.md +131 -0
- package/templates/_base/.cursor/agents/skills/shadcn/SKILL.md +241 -0
- package/templates/_base/.cursor/agents/skills/shadcn/agents/openai.yml +5 -0
- package/templates/_base/.cursor/agents/skills/shadcn/assets/shadcn-small.png +0 -0
- package/templates/_base/.cursor/agents/skills/shadcn/assets/shadcn.png +0 -0
- package/templates/_base/.cursor/agents/skills/shadcn/cli.md +257 -0
- package/templates/_base/.cursor/agents/skills/shadcn/customization.md +202 -0
- package/templates/_base/.cursor/agents/skills/shadcn/evals/evals.json +47 -0
- package/templates/_base/.cursor/agents/skills/shadcn/mcp.md +94 -0
- package/templates/_base/.cursor/agents/skills/shadcn/rules/base-vs-radix.md +306 -0
- package/templates/_base/.cursor/agents/skills/shadcn/rules/composition.md +195 -0
- package/templates/_base/.cursor/agents/skills/shadcn/rules/forms.md +192 -0
- package/templates/_base/.cursor/agents/skills/shadcn/rules/icons.md +101 -0
- package/templates/_base/.cursor/agents/skills/shadcn/rules/styling.md +162 -0
- package/templates/_base/.cursor/commands/builder.md +0 -0
- package/templates/_base/.cursor/commands/pr.md +7 -0
- package/templates/_base/.cursor/rules/api_architecture.mdc +268 -0
- package/templates/_base/.cursor/rules/coding_standards.mdc +64 -0
- package/templates/_base/.cursor/rules/convex_rules.mdc +675 -0
- package/templates/_base/.cursor/rules/frontend_rules.mdc +268 -0
- package/templates/_base/.env.convex.example +3 -0
- package/templates/_base/.github/workflows/ci.yml +29 -0
- package/templates/_base/.nvmrc +1 -0
- package/templates/_base/.vscode/settings.json +9 -0
- package/templates/_base/apps/api/auth.config.ts +18 -0
- package/templates/_base/apps/api/functions.ts +99 -0
- package/templates/_base/apps/api/project.json +22 -0
- package/templates/_base/apps/api/schema.ts +11 -0
- package/templates/_base/apps/api/todos/crud.ts +81 -0
- package/templates/_base/apps/api/todos/schema.ts +11 -0
- package/templates/_base/apps/api/todos/types.ts +22 -0
- package/templates/_base/apps/api/tsconfig.json +23 -0
- package/templates/_base/apps/api/types.ts +16 -0
- package/templates/_base/biome.json +114 -0
- package/templates/_base/convex.json +4 -0
- package/templates/_base/emails/project.json +16 -0
- package/templates/_base/emails/tsconfig.json +5 -0
- package/templates/_base/emails/welcome_email.tsx +53 -0
- package/templates/_base/nx.json +29 -0
- package/templates/_base/package.json +73 -0
- package/templates/_base/scripts/sync_convex_env.ts +63 -0
- package/templates/_base/shared/assets/image.d.ts +4 -0
- package/templates/_base/shared/assets/src/styles/global.css +73 -0
- package/templates/_base/shared/assets/tsconfig.json +5 -0
- package/templates/_base/shared/ui/src/base/alert_dialog.tsx +139 -0
- package/templates/_base/shared/ui/src/base/badge.tsx +33 -0
- package/templates/_base/shared/ui/src/base/basic_data_table.tsx +61 -0
- package/templates/_base/shared/ui/src/base/button.tsx +69 -0
- package/templates/_base/shared/ui/src/base/button_group.tsx +82 -0
- package/templates/_base/shared/ui/src/base/card.tsx +79 -0
- package/templates/_base/shared/ui/src/base/checkbox.tsx +26 -0
- package/templates/_base/shared/ui/src/base/command.tsx +165 -0
- package/templates/_base/shared/ui/src/base/dialog.tsx +129 -0
- package/templates/_base/shared/ui/src/base/dropdown_menu.tsx +232 -0
- package/templates/_base/shared/ui/src/base/form.tsx +161 -0
- package/templates/_base/shared/ui/src/base/input.tsx +129 -0
- package/templates/_base/shared/ui/src/base/label.tsx +19 -0
- package/templates/_base/shared/ui/src/base/popover.tsx +46 -0
- package/templates/_base/shared/ui/src/base/radio_group.tsx +49 -0
- package/templates/_base/shared/ui/src/base/resizable.tsx +55 -0
- package/templates/_base/shared/ui/src/base/scroll_area.tsx +44 -0
- package/templates/_base/shared/ui/src/base/select.tsx +151 -0
- package/templates/_base/shared/ui/src/base/separator.tsx +32 -0
- package/templates/_base/shared/ui/src/base/sheet.tsx +130 -0
- package/templates/_base/shared/ui/src/base/side_bar.tsx +688 -0
- package/templates/_base/shared/ui/src/base/skeleton.tsx +7 -0
- package/templates/_base/shared/ui/src/base/spinner.tsx +20 -0
- package/templates/_base/shared/ui/src/base/switch.tsx +27 -0
- package/templates/_base/shared/ui/src/base/table.tsx +91 -0
- package/templates/_base/shared/ui/src/base/text_area.tsx +21 -0
- package/templates/_base/shared/ui/src/base/tooltip.tsx +31 -0
- package/templates/_base/shared/ui/src/base/utils.ts +17 -0
- package/templates/_base/shared/ui/src/hooks/use_keyboard_press.tsx +48 -0
- package/templates/_base/shared/ui/src/hooks/use_keyboard_release.tsx +48 -0
- package/templates/_base/shared/ui/src/hooks/use_mobile.tsx +25 -0
- package/templates/_base/shared/ui/src/hooks/use_mouse_click.tsx +44 -0
- package/templates/_base/shared/ui/src/hooks/use_mouse_location.tsx +55 -0
- package/templates/_base/shared/ui/src/hooks/use_outside_click.tsx +29 -0
- package/templates/_base/shared/ui/src/hooks/use_query_params.tsx +33 -0
- package/templates/_base/shared/ui/tsconfig.json +8 -0
- package/templates/_base/shared/utils/src/convex.ts +3 -0
- package/templates/_base/shared/utils/src/time.ts +12 -0
- package/templates/_base/shared/utils/tsconfig.json +5 -0
- package/templates/_base/skills-lock.json +35 -0
- package/templates/_base/tsconfig.base.json +34 -0
- package/templates/nextjs/.env.example +8 -0
- package/templates/nextjs/index.d.ts +6 -0
- package/templates/nextjs/next-env.d.ts +5 -0
- package/templates/nextjs/next.config.js +22 -0
- package/templates/nextjs/postcss.config.js +17 -0
- package/templates/nextjs/project.json +22 -0
- package/templates/nextjs/src/app/(auth)/layout.tsx +21 -0
- package/templates/nextjs/src/app/(auth)/not-allowed/page.tsx +22 -0
- package/templates/nextjs/src/app/(auth)/sign-in/[[...sign-in]]/page.tsx +15 -0
- package/templates/nextjs/src/app/(dashboard)/layout.tsx +27 -0
- package/templates/nextjs/src/app/(dashboard)/page.tsx +5 -0
- package/templates/nextjs/src/app/(dashboard)/todos/[id]/page.tsx +23 -0
- package/templates/nextjs/src/app/(dashboard)/todos/page.tsx +16 -0
- package/templates/nextjs/src/app/app.css +3 -0
- package/templates/nextjs/src/app/layout.tsx +26 -0
- package/templates/nextjs/src/convex.ts +11 -0
- package/templates/nextjs/src/middleware.ts +18 -0
- package/templates/nextjs/src/providers/convex_provider.tsx +44 -0
- package/templates/nextjs/src/surfaces/home_surface.tsx +22 -0
- package/templates/nextjs/src/surfaces/todos/all_todos_surface.tsx +97 -0
- package/templates/nextjs/src/surfaces/todos/create_todo_sheet.tsx +107 -0
- package/templates/nextjs/src/surfaces/todos/single_todo_surface.tsx +90 -0
- package/templates/nextjs/src/ui/sidebar/nav_link.tsx +36 -0
- package/templates/nextjs/src/ui/sidebar/sidebar.tsx +125 -0
- package/templates/nextjs/src/utils/font.ts +9 -0
- package/templates/nextjs/tsconfig.json +42 -0
- package/templates/react-router/.env.example +8 -0
- package/templates/react-router/postcss.config.js +15 -0
- package/templates/react-router/project.json +23 -0
- package/templates/react-router/public/favicon.ico +0 -0
- package/templates/react-router/react-router.config.ts +9 -0
- package/templates/react-router/src/app.css +3 -0
- package/templates/react-router/src/components/error_boundary.tsx +33 -0
- package/templates/react-router/src/layouts/sidebar/sidebar_aside/sidebar_aside.tsx +76 -0
- package/templates/react-router/src/layouts/sidebar/sidebar_aside/user_menu.tsx +36 -0
- package/templates/react-router/src/layouts/sidebar/sidebar_layout.tsx +22 -0
- package/templates/react-router/src/providers/api_auth_provider.tsx +38 -0
- package/templates/react-router/src/root.tsx +37 -0
- package/templates/react-router/src/routes/auth/layout.tsx +13 -0
- package/templates/react-router/src/routes/auth/sign-in.tsx +13 -0
- package/templates/react-router/src/routes/index.tsx +9 -0
- package/templates/react-router/src/routes/layout.tsx +26 -0
- package/templates/react-router/src/routes/todos/[id].tsx +22 -0
- package/templates/react-router/src/routes/todos/index.tsx +13 -0
- package/templates/react-router/src/routes.ts +12 -0
- package/templates/react-router/src/surfaces/home_surface.tsx +20 -0
- package/templates/react-router/src/surfaces/todos/all_todos_surface.tsx +87 -0
- package/templates/react-router/src/surfaces/todos/create_todo_sheet.tsx +102 -0
- package/templates/react-router/src/surfaces/todos/single_todo_surface.tsx +81 -0
- package/templates/react-router/tsconfig.json +20 -0
- package/templates/react-router/vite.config.ts +40 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Aron Weston 2025.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Loader2Icon } from "lucide-react";
|
|
6
|
+
|
|
7
|
+
import { cn } from "@/ui/base/utils";
|
|
8
|
+
|
|
9
|
+
function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
|
|
10
|
+
return (
|
|
11
|
+
<Loader2Icon
|
|
12
|
+
role="status"
|
|
13
|
+
aria-label="Loading"
|
|
14
|
+
className={cn("size-4 animate-spin", className)}
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { Spinner };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as SwitchPrimitives from "@radix-ui/react-switch";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
|
|
4
|
+
import { cn } from "@/ui/base/utils";
|
|
5
|
+
|
|
6
|
+
const Switch = React.forwardRef<
|
|
7
|
+
React.ElementRef<typeof SwitchPrimitives.Root>,
|
|
8
|
+
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
|
|
9
|
+
>(({ className, ...props }, ref) => (
|
|
10
|
+
<SwitchPrimitives.Root
|
|
11
|
+
className={cn(
|
|
12
|
+
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-color_tool.tsx focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
|
|
13
|
+
className,
|
|
14
|
+
)}
|
|
15
|
+
{...props}
|
|
16
|
+
ref={ref}
|
|
17
|
+
>
|
|
18
|
+
<SwitchPrimitives.Thumb
|
|
19
|
+
className={cn(
|
|
20
|
+
"pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0",
|
|
21
|
+
)}
|
|
22
|
+
/>
|
|
23
|
+
</SwitchPrimitives.Root>
|
|
24
|
+
));
|
|
25
|
+
Switch.displayName = SwitchPrimitives.Root.displayName;
|
|
26
|
+
|
|
27
|
+
export { Switch };
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/ui/base/utils";
|
|
4
|
+
|
|
5
|
+
const Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(
|
|
6
|
+
({ className, ...props }, ref) => (
|
|
7
|
+
<div className="relative w-full overflow-auto">
|
|
8
|
+
<table ref={ref} className={cn("w-full caption-bottom text-sm", className)} {...props} />
|
|
9
|
+
</div>
|
|
10
|
+
),
|
|
11
|
+
);
|
|
12
|
+
Table.displayName = "Table";
|
|
13
|
+
|
|
14
|
+
const TableHeader = React.forwardRef<
|
|
15
|
+
HTMLTableSectionElement,
|
|
16
|
+
React.HTMLAttributes<HTMLTableSectionElement>
|
|
17
|
+
>(({ className, ...props }, ref) => (
|
|
18
|
+
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
|
|
19
|
+
));
|
|
20
|
+
TableHeader.displayName = "TableHeader";
|
|
21
|
+
|
|
22
|
+
const TableBody = React.forwardRef<
|
|
23
|
+
HTMLTableSectionElement,
|
|
24
|
+
React.HTMLAttributes<HTMLTableSectionElement>
|
|
25
|
+
>(({ className, ...props }, ref) => (
|
|
26
|
+
<tbody ref={ref} className={cn("[&_tr:last-child]:border-0", className)} {...props} />
|
|
27
|
+
));
|
|
28
|
+
TableBody.displayName = "TableBody";
|
|
29
|
+
|
|
30
|
+
const TableFooter = React.forwardRef<
|
|
31
|
+
HTMLTableSectionElement,
|
|
32
|
+
React.HTMLAttributes<HTMLTableSectionElement>
|
|
33
|
+
>(({ className, ...props }, ref) => (
|
|
34
|
+
<tfoot
|
|
35
|
+
ref={ref}
|
|
36
|
+
className={cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className)}
|
|
37
|
+
{...props}
|
|
38
|
+
/>
|
|
39
|
+
));
|
|
40
|
+
TableFooter.displayName = "TableFooter";
|
|
41
|
+
|
|
42
|
+
const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(
|
|
43
|
+
({ className, ...props }, ref) => (
|
|
44
|
+
<tr
|
|
45
|
+
ref={ref}
|
|
46
|
+
className={cn(
|
|
47
|
+
"border-b transition-color_tool.tsx hover:bg-muted/50 data-[design_doc=selected]:bg-muted",
|
|
48
|
+
className,
|
|
49
|
+
)}
|
|
50
|
+
{...props}
|
|
51
|
+
/>
|
|
52
|
+
),
|
|
53
|
+
);
|
|
54
|
+
TableRow.displayName = "TableRow";
|
|
55
|
+
|
|
56
|
+
const TableHead = React.forwardRef<
|
|
57
|
+
HTMLTableCellElement,
|
|
58
|
+
React.ThHTMLAttributes<HTMLTableCellElement>
|
|
59
|
+
>(({ className, ...props }, ref) => (
|
|
60
|
+
<th
|
|
61
|
+
ref={ref}
|
|
62
|
+
className={cn(
|
|
63
|
+
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
|
|
64
|
+
className,
|
|
65
|
+
)}
|
|
66
|
+
{...props}
|
|
67
|
+
/>
|
|
68
|
+
));
|
|
69
|
+
TableHead.displayName = "TableHead";
|
|
70
|
+
|
|
71
|
+
const TableCell = React.forwardRef<
|
|
72
|
+
HTMLTableCellElement,
|
|
73
|
+
React.TdHTMLAttributes<HTMLTableCellElement>
|
|
74
|
+
>(({ className, ...props }, ref) => (
|
|
75
|
+
<td
|
|
76
|
+
ref={ref}
|
|
77
|
+
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
|
|
78
|
+
{...props}
|
|
79
|
+
/>
|
|
80
|
+
));
|
|
81
|
+
TableCell.displayName = "TableCell";
|
|
82
|
+
|
|
83
|
+
const TableCaption = React.forwardRef<
|
|
84
|
+
HTMLTableCaptionElement,
|
|
85
|
+
React.HTMLAttributes<HTMLTableCaptionElement>
|
|
86
|
+
>(({ className, ...props }, ref) => (
|
|
87
|
+
<caption ref={ref} className={cn("mt-4 text-sm text-muted-foreground", className)} {...props} />
|
|
88
|
+
));
|
|
89
|
+
TableCaption.displayName = "TableCaption";
|
|
90
|
+
|
|
91
|
+
export { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/ui/base/utils";
|
|
4
|
+
|
|
5
|
+
const TextArea = React.forwardRef<HTMLTextAreaElement, React.ComponentProps<"textarea">>(
|
|
6
|
+
({ className, ...props }, ref) => {
|
|
7
|
+
return (
|
|
8
|
+
<textarea
|
|
9
|
+
className={cn(
|
|
10
|
+
"flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
11
|
+
className,
|
|
12
|
+
)}
|
|
13
|
+
ref={ref}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
);
|
|
17
|
+
},
|
|
18
|
+
);
|
|
19
|
+
TextArea.displayName = "TextArea";
|
|
20
|
+
|
|
21
|
+
export { TextArea };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
|
|
4
|
+
import { cn } from "@/ui/base/utils";
|
|
5
|
+
|
|
6
|
+
const TooltipProvider = TooltipPrimitive.Provider;
|
|
7
|
+
|
|
8
|
+
const Tooltip = TooltipPrimitive.Root;
|
|
9
|
+
|
|
10
|
+
const TooltipTrigger = TooltipPrimitive.Trigger;
|
|
11
|
+
|
|
12
|
+
const TooltipContent = React.forwardRef<
|
|
13
|
+
React.ElementRef<typeof TooltipPrimitive.Content>,
|
|
14
|
+
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
|
15
|
+
>(({ className, sideOffset = 4, children, ...props }, ref) => (
|
|
16
|
+
<TooltipPrimitive.Content
|
|
17
|
+
ref={ref}
|
|
18
|
+
sideOffset={sideOffset}
|
|
19
|
+
className={cn(
|
|
20
|
+
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground animate-in fade-in-0 zoom-in-95 data-[design_doc=closed]:animate-out data-[design_doc=closed]:fade-out-0 data-[design_doc=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
21
|
+
className,
|
|
22
|
+
)}
|
|
23
|
+
{...props}
|
|
24
|
+
>
|
|
25
|
+
{children}
|
|
26
|
+
<TooltipPrimitive.Arrow className="bg-white" width={11} height={5} />
|
|
27
|
+
</TooltipPrimitive.Content>
|
|
28
|
+
));
|
|
29
|
+
|
|
30
|
+
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
|
31
|
+
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type ClassValue, clsx } from "clsx";
|
|
2
|
+
import { twMerge } from "tailwind-merge";
|
|
3
|
+
|
|
4
|
+
export function cn(...inputs: ClassValue[]) {
|
|
5
|
+
return twMerge(clsx(inputs));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function formatList(items: string[], locale = "en"): string {
|
|
9
|
+
return new Intl.ListFormat(locale, {
|
|
10
|
+
style: "long",
|
|
11
|
+
type: "conjunction",
|
|
12
|
+
}).format(items);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function capitaliseFirst(s: string): string {
|
|
16
|
+
return s.length > 0 ? s[0].toUpperCase() + s.slice(1) : s;
|
|
17
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Store } from "@tanstack/react-store";
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
|
|
4
|
+
export type KeyboardSubscriberCallback = (
|
|
5
|
+
key: string,
|
|
6
|
+
shift: boolean,
|
|
7
|
+
alt: boolean,
|
|
8
|
+
target: EventTarget | null,
|
|
9
|
+
) => void;
|
|
10
|
+
|
|
11
|
+
export const keyboardSubscribers = new Store(
|
|
12
|
+
new Set<KeyboardSubscriberCallback>(),
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Only use once in a project, likely at the surface entrypoint
|
|
17
|
+
*/
|
|
18
|
+
export const useKeyboardPressObserver = () => {
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const listener = (e: KeyboardEvent) => {
|
|
21
|
+
for (const subscriber of keyboardSubscribers.state) {
|
|
22
|
+
subscriber(e.key, e.shiftKey, e.altKey, e.target);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
document.addEventListener("keydown", listener);
|
|
27
|
+
|
|
28
|
+
return () => {
|
|
29
|
+
document.removeEventListener("keydown", listener);
|
|
30
|
+
keyboardSubscribers.state.clear();
|
|
31
|
+
};
|
|
32
|
+
}, []);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const useKeyboardPressSubscriber = (
|
|
36
|
+
cb: KeyboardSubscriberCallback,
|
|
37
|
+
deps: any[],
|
|
38
|
+
) => {
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
keyboardSubscribers.state.add(cb);
|
|
41
|
+
|
|
42
|
+
return () => {
|
|
43
|
+
keyboardSubscribers.state.delete(cb);
|
|
44
|
+
};
|
|
45
|
+
}, deps);
|
|
46
|
+
|
|
47
|
+
return () => keyboardSubscribers.state.delete(cb);
|
|
48
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Store } from "@tanstack/react-store";
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
|
|
4
|
+
export type KeyboardSubscriberCallback = (
|
|
5
|
+
key: string,
|
|
6
|
+
shift: boolean,
|
|
7
|
+
alt: boolean,
|
|
8
|
+
target: EventTarget | null,
|
|
9
|
+
) => void;
|
|
10
|
+
|
|
11
|
+
export const keyboardSubscribers = new Store(
|
|
12
|
+
new Set<KeyboardSubscriberCallback>(),
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Only use once in a project, likely at the surface entrypoint
|
|
17
|
+
*/
|
|
18
|
+
export const useKeyboardReleaseObserver = () => {
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const listener = (e: KeyboardEvent) => {
|
|
21
|
+
for (const subscriber of keyboardSubscribers.state) {
|
|
22
|
+
subscriber(e.key, e.shiftKey, e.altKey, e.target);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
document.addEventListener("keyup", listener);
|
|
27
|
+
|
|
28
|
+
return () => {
|
|
29
|
+
document.removeEventListener("keyup", listener);
|
|
30
|
+
keyboardSubscribers.state.clear();
|
|
31
|
+
};
|
|
32
|
+
}, []);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const useKeyboardReleaseSubscriber = (
|
|
36
|
+
cb: KeyboardSubscriberCallback,
|
|
37
|
+
deps: any[],
|
|
38
|
+
) => {
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
keyboardSubscribers.state.add(cb);
|
|
41
|
+
|
|
42
|
+
return () => {
|
|
43
|
+
keyboardSubscribers.state.delete(cb);
|
|
44
|
+
};
|
|
45
|
+
}, deps);
|
|
46
|
+
|
|
47
|
+
return () => keyboardSubscribers.state.delete(cb);
|
|
48
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Aron Weston 2025.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
"use client";
|
|
6
|
+
|
|
7
|
+
import { useEffect, useState } from "react";
|
|
8
|
+
|
|
9
|
+
const MOBILE_BREAKPOINT = 768;
|
|
10
|
+
|
|
11
|
+
export const useMobile = () => {
|
|
12
|
+
const [isMobile, setIsMobile] = useState<boolean | undefined>(undefined);
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
|
|
16
|
+
const onChange = () => {
|
|
17
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
|
18
|
+
};
|
|
19
|
+
mql.addEventListener("change", onChange);
|
|
20
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
|
21
|
+
return () => mql.removeEventListener("change", onChange);
|
|
22
|
+
}, []);
|
|
23
|
+
|
|
24
|
+
return !!isMobile;
|
|
25
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Store } from "@tanstack/react-store";
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
|
|
4
|
+
export type MouseSubscriberCallback = (
|
|
5
|
+
position: [number, number],
|
|
6
|
+
target: EventTarget | null,
|
|
7
|
+
) => void;
|
|
8
|
+
|
|
9
|
+
export const mouseSubscribers = new Store(new Set<MouseSubscriberCallback>());
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Only use once in a project, likely at the surface entrypoint
|
|
13
|
+
*/
|
|
14
|
+
export const useMouseClickObserver = () => {
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
const listener = (e: MouseEvent) => {
|
|
17
|
+
for (const subscriber of mouseSubscribers.state) {
|
|
18
|
+
subscriber([e.clientX, e.clientY], e.target);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
document.addEventListener("mousedown", listener);
|
|
23
|
+
|
|
24
|
+
return () => {
|
|
25
|
+
document.removeEventListener("mousedown", listener);
|
|
26
|
+
mouseSubscribers.state.clear();
|
|
27
|
+
};
|
|
28
|
+
}, []);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const useMouseClickSubscriber = (
|
|
32
|
+
cb: MouseSubscriberCallback,
|
|
33
|
+
deps: any[],
|
|
34
|
+
) => {
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
mouseSubscribers.state.add(cb);
|
|
37
|
+
|
|
38
|
+
return () => {
|
|
39
|
+
mouseSubscribers.state.delete(cb);
|
|
40
|
+
};
|
|
41
|
+
}, deps);
|
|
42
|
+
|
|
43
|
+
return () => mouseSubscribers.state.delete(cb);
|
|
44
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Store } from "@tanstack/react-store";
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
|
|
4
|
+
export type MouseLocationState = {
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const initialMouseLocationState: MouseLocationState = {
|
|
10
|
+
x: 0,
|
|
11
|
+
y: 0,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const mouseLocationState = new Store<MouseLocationState>(
|
|
15
|
+
initialMouseLocationState,
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
export const mouseLocationSubscribers = new Store(
|
|
19
|
+
new Set<(state: MouseLocationState) => void>(),
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
export const setMouse = (x: number, y: number) =>
|
|
23
|
+
mouseLocationState.setState(() => ({ x, y }));
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Only use once in a project, likely at the surface entrypoint
|
|
27
|
+
*/
|
|
28
|
+
export const useMouseLocationObserver = () => {
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
const listener = (e: MouseEvent) => {
|
|
31
|
+
setMouse(e.clientX, e.clientY);
|
|
32
|
+
|
|
33
|
+
for (const subscriber of mouseLocationSubscribers.state) {
|
|
34
|
+
subscriber(mouseLocationState.state);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
document.addEventListener("mousemove", listener);
|
|
39
|
+
|
|
40
|
+
return () => {
|
|
41
|
+
document.removeEventListener("mousemove", listener);
|
|
42
|
+
mouseLocationSubscribers.state.clear();
|
|
43
|
+
};
|
|
44
|
+
}, []);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const useMouseLocationSubscriber = (
|
|
48
|
+
cb: (state: MouseLocationState) => void,
|
|
49
|
+
) => {
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
mouseLocationSubscribers.state.add(cb);
|
|
52
|
+
}, []);
|
|
53
|
+
|
|
54
|
+
return () => mouseLocationSubscribers.state.delete(cb);
|
|
55
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { RefObject } from "react";
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Hook that alerts clicks outside the passed ref
|
|
6
|
+
*/
|
|
7
|
+
export const useOutsideClick = (
|
|
8
|
+
ref: RefObject<HTMLDivElement>,
|
|
9
|
+
cb: () => void,
|
|
10
|
+
) => {
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
/**
|
|
13
|
+
* Alert if clicked on outside of element
|
|
14
|
+
*/
|
|
15
|
+
function handleClickOutside(event: any) {
|
|
16
|
+
if (ref.current && !ref.current.contains(event.target)) {
|
|
17
|
+
cb();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Bind the event listener
|
|
22
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
23
|
+
|
|
24
|
+
return () => {
|
|
25
|
+
// Unbind the event listener on clean up
|
|
26
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
27
|
+
};
|
|
28
|
+
}, [ref]);
|
|
29
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Aron Weston 2025.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useCallback } from "react";
|
|
6
|
+
import { usePathname, useSearchParams } from "next/navigation";
|
|
7
|
+
import { useRouter } from "next/navigation";
|
|
8
|
+
|
|
9
|
+
export const useQueryParams = () => {
|
|
10
|
+
const router = useRouter();
|
|
11
|
+
const pathname = usePathname();
|
|
12
|
+
const searchParams = useSearchParams();
|
|
13
|
+
|
|
14
|
+
return useCallback(
|
|
15
|
+
(paths: Record<string, string | null>) => {
|
|
16
|
+
const params = new URLSearchParams(searchParams.toString());
|
|
17
|
+
|
|
18
|
+
for (const [key, value] of Object.entries(paths)) {
|
|
19
|
+
if (value === null) {
|
|
20
|
+
params.delete(key);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
params.set(key, value);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const path = pathname + "?" + params.toString();
|
|
28
|
+
|
|
29
|
+
return router.push(path);
|
|
30
|
+
},
|
|
31
|
+
[searchParams],
|
|
32
|
+
);
|
|
33
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import dayjs from "dayjs";
|
|
2
|
+
import relativeTime from "dayjs/plugin/relativeTime";
|
|
3
|
+
|
|
4
|
+
dayjs.extend(relativeTime);
|
|
5
|
+
|
|
6
|
+
export const formatTime = (date: Date | number) => {
|
|
7
|
+
return dayjs(date).format("DD/MM/YYYY hh:mma");
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const formatDistanceToNow = (timestamp: number) => {
|
|
11
|
+
return dayjs(timestamp).fromNow();
|
|
12
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"skills": {
|
|
4
|
+
"shadcn": {
|
|
5
|
+
"source": "shadcn/ui",
|
|
6
|
+
"sourceType": "github",
|
|
7
|
+
"computedHash": "1ec42c9b918b315d8e5c6c856b78c3c4725fc93dea30b9b7e9975c69848fcce6"
|
|
8
|
+
},
|
|
9
|
+
"clerk": {
|
|
10
|
+
"source": "clerk/skills",
|
|
11
|
+
"sourceType": "github",
|
|
12
|
+
"computedHash": "912205692b6c1db07d6933f0584af27c6582484e5d6fac1c13fec4b6a963a528"
|
|
13
|
+
},
|
|
14
|
+
"clerk-backend-api": {
|
|
15
|
+
"source": "clerk/skills",
|
|
16
|
+
"sourceType": "github",
|
|
17
|
+
"computedHash": "4f51a8aac3bb33c5dd6ec06740e13db9c45ad40bb372c918155ea8076f80367b"
|
|
18
|
+
},
|
|
19
|
+
"clerk-custom-ui": {
|
|
20
|
+
"source": "clerk/skills",
|
|
21
|
+
"sourceType": "github",
|
|
22
|
+
"computedHash": "bf28bf1908604c50876a302c8bebd90cf31b04fcf0482ccfc542b22778677bcb"
|
|
23
|
+
},
|
|
24
|
+
"clerk-nextjs-patterns": {
|
|
25
|
+
"source": "clerk/skills",
|
|
26
|
+
"sourceType": "github",
|
|
27
|
+
"computedHash": "c55aeca9c8ca0602af4a2bdc0370e3c349e53752177cb1187edf213f51d34636"
|
|
28
|
+
},
|
|
29
|
+
"clerk-webhooks": {
|
|
30
|
+
"source": "clerk/skills",
|
|
31
|
+
"sourceType": "github",
|
|
32
|
+
"computedHash": "6a13eea450dda0b57edc358970f53c55ad7a2eb237d31decbc38e312555ddac5"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compileOnSave": false,
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": ".",
|
|
5
|
+
"baseUrl": ".",
|
|
6
|
+
"moduleResolution": "Bundler",
|
|
7
|
+
"module": "ESNext",
|
|
8
|
+
"target": "ESNext",
|
|
9
|
+
"jsx": "react-jsx",
|
|
10
|
+
"allowJs": true,
|
|
11
|
+
"lib": ["ESNext", "DOM"],
|
|
12
|
+
"strict": true,
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"strictNullChecks": true,
|
|
15
|
+
"allowSyntheticDefaultImports": true,
|
|
16
|
+
"isolatedModules": true,
|
|
17
|
+
"resolveJsonModule": true,
|
|
18
|
+
"paths": {
|
|
19
|
+
"@/api/*": ["./apps/api/*"],
|
|
20
|
+
"@/assets/*": ["./shared/assets/src/*"],
|
|
21
|
+
"@/ui/*": ["./shared/ui/src/*"],
|
|
22
|
+
"@/utils/*": ["./shared/utils/src/*"],
|
|
23
|
+
"@/emails/*": ["./emails/*"],
|
|
24
|
+
"@/web/*": ["./apps/web/src/*"]
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"exclude": ["node_modules", "dist", "build", "public"],
|
|
28
|
+
"include": [
|
|
29
|
+
"./apps/api/src/**/*",
|
|
30
|
+
"./apps/web/src/**/*",
|
|
31
|
+
"./shared/**/*",
|
|
32
|
+
"./tsconfig.json"
|
|
33
|
+
]
|
|
34
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
4
|
+
const { composePlugins, withNx } = require("@nx/next");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @type {import('@nx/next/plugins/with-nx').WithNxOptions}
|
|
8
|
+
**/
|
|
9
|
+
const nextConfig = {
|
|
10
|
+
nx: {
|
|
11
|
+
// Set this to true if you would like to use SVGR
|
|
12
|
+
// See: https://github.com/gregberge/svgr
|
|
13
|
+
svgr: false,
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const plugins = [
|
|
18
|
+
// Add more Next.js plugins to this list if needed.
|
|
19
|
+
withNx,
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
module.exports = composePlugins(...plugins)(nextConfig);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Aron Weston 2025.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { join } = require("node:path");
|
|
6
|
+
|
|
7
|
+
// Note: If you use library-specific PostCSS/Tailwind configuration then you should remove the `postcssConfig` build
|
|
8
|
+
// option from your application's configuration (i.e. project.json).
|
|
9
|
+
//
|
|
10
|
+
// See: https://nx.dev/guides/using-tailwind-css-in-react#step-4:-applying-configuration-to-libraries
|
|
11
|
+
|
|
12
|
+
module.exports = {
|
|
13
|
+
plugins: {
|
|
14
|
+
"@tailwindcss/postcss": {},
|
|
15
|
+
autoprefixer: {},
|
|
16
|
+
},
|
|
17
|
+
};
|