create-fate 1.5.2 → 1.6.0
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/README.md +1 -2
- package/bin/create-fate.mjs +14 -50
- package/package.json +1 -1
- package/templates/fate/README.md +1 -2
- package/templates/fate/_shared/vue-app/package.json +4 -3
- package/templates/fate/_shared/vue-app/vite.config.ts +18 -11
- package/templates/fate/drizzle/client/package.json +6 -6
- package/templates/fate/graphql/client/package.json +6 -6
- package/templates/fate/graphql/server/.gitignore +5 -0
- package/templates/fate/graphql/server/node_modules/.bin/esbuild +22 -0
- package/templates/fate/graphql/server/node_modules/.bin/eslint +22 -0
- package/templates/fate/graphql/server/node_modules/.bin/fate +22 -0
- package/templates/fate/graphql/server/node_modules/.bin/intent +22 -0
- package/templates/fate/graphql/server/node_modules/.bin/jiti +22 -0
- package/templates/fate/graphql/server/node_modules/.bin/nft +22 -0
- package/templates/fate/graphql/server/node_modules/.bin/nodemon +22 -0
- package/templates/fate/graphql/server/node_modules/.bin/oxfmt +22 -0
- package/templates/fate/graphql/server/node_modules/.bin/oxlint +22 -0
- package/templates/fate/graphql/server/node_modules/.bin/prisma +22 -0
- package/templates/fate/graphql/server/node_modules/.bin/prisma-json-types-generator +22 -0
- package/templates/fate/graphql/server/node_modules/.bin/prisma-pothos-types +22 -0
- package/templates/fate/graphql/server/node_modules/.bin/tsc +22 -0
- package/templates/fate/graphql/server/node_modules/.bin/tsgolint +22 -0
- package/templates/fate/graphql/server/node_modules/.bin/tsx +22 -0
- package/templates/fate/graphql/server/node_modules/.bin/vitest +22 -0
- package/templates/fate/graphql/server/node_modules/.bin/vp +22 -0
- package/templates/fate/graphql/server/node_modules/.bin/vpr +22 -0
- package/templates/fate/graphql/server/node_modules/.bin/yaml +22 -0
- package/templates/fate/graphql-client/README.md +11 -1
- package/templates/fate/graphql-client/env.ts +6 -0
- package/templates/fate/graphql-client/package.json +6 -6
- package/templates/fate/http/client/package.json +6 -6
- package/templates/fate/prisma/client/package.json +6 -6
- package/templates/fate/void/README.md +12 -0
- package/templates/fate/void/db/migrations/20260918045815_legal_dragon_lord.sql +15 -0
- package/templates/fate/{cloudflare/server/db/migrations/meta/20260508073357_snapshot.json → void/db/migrations/meta/20260918045815_snapshot.json} +174 -124
- package/templates/fate/void/db/migrations/meta/_journal.json +7 -0
- package/templates/fate/void/package.json +6 -7
- package/templates/fate/cloudflare/.github/workflows/push.yml +0 -27
- package/templates/fate/cloudflare/.vite-hooks/pre-commit +0 -1
- package/templates/fate/cloudflare/.vscode/extensions.json +0 -12
- package/templates/fate/cloudflare/.vscode/settings.json +0 -25
- package/templates/fate/cloudflare/AGENTS.md +0 -55
- package/templates/fate/cloudflare/README.md +0 -137
- package/templates/fate/cloudflare/_gitignore +0 -20
- package/templates/fate/cloudflare/client/node_modules/.bin/browserslist +0 -22
- package/templates/fate/cloudflare/client/node_modules/.bin/cf-wrangler +0 -22
- package/templates/fate/cloudflare/client/node_modules/.bin/drizzle-kit +0 -22
- package/templates/fate/cloudflare/client/node_modules/.bin/esbuild +0 -22
- package/templates/fate/cloudflare/client/node_modules/.bin/fate +0 -22
- package/templates/fate/cloudflare/client/node_modules/.bin/fbtee +0 -22
- package/templates/fate/cloudflare/client/node_modules/.bin/intent +0 -22
- package/templates/fate/cloudflare/client/node_modules/.bin/jiti +0 -22
- package/templates/fate/cloudflare/client/node_modules/.bin/oxfmt +0 -22
- package/templates/fate/cloudflare/client/node_modules/.bin/oxlint +0 -22
- package/templates/fate/cloudflare/client/node_modules/.bin/rolldown +0 -22
- package/templates/fate/cloudflare/client/node_modules/.bin/tsc +0 -22
- package/templates/fate/cloudflare/client/node_modules/.bin/tsx +0 -22
- package/templates/fate/cloudflare/client/node_modules/.bin/vitest +0 -22
- package/templates/fate/cloudflare/client/node_modules/.bin/void +0 -22
- package/templates/fate/cloudflare/client/node_modules/.bin/vp +0 -22
- package/templates/fate/cloudflare/client/node_modules/.bin/vpr +0 -22
- package/templates/fate/cloudflare/client/node_modules/.bin/workerd +0 -22
- package/templates/fate/cloudflare/client/node_modules/.bin/wrangler +0 -22
- package/templates/fate/cloudflare/client/node_modules/.bin/wrangler2 +0 -22
- package/templates/fate/cloudflare/client/package.json +0 -62
- package/templates/fate/cloudflare/client/pages/index.tsx +0 -96
- package/templates/fate/cloudflare/client/pages/layout.tsx +0 -68
- package/templates/fate/cloudflare/client/pages/login.tsx +0 -107
- package/templates/fate/cloudflare/client/pages/post/[id].tsx +0 -22
- package/templates/fate/cloudflare/client/pages/search.tsx +0 -99
- package/templates/fate/cloudflare/client/public/icon.svg +0 -1
- package/templates/fate/cloudflare/client/src/App.css +0 -153
- package/templates/fate/cloudflare/client/src/lib/AvailableLanguages.tsx +0 -5
- package/templates/fate/cloudflare/client/src/lib/LocaleContext.tsx +0 -61
- package/templates/fate/cloudflare/client/src/lib/cx.tsx +0 -6
- package/templates/fate/cloudflare/client/src/lib/env.tsx +0 -5
- package/templates/fate/cloudflare/client/src/ui/Badge.tsx +0 -31
- package/templates/fate/cloudflare/client/src/ui/Button.tsx +0 -108
- package/templates/fate/cloudflare/client/src/ui/Card.tsx +0 -32
- package/templates/fate/cloudflare/client/src/ui/CommentCard.tsx +0 -75
- package/templates/fate/cloudflare/client/src/ui/Error.tsx +0 -19
- package/templates/fate/cloudflare/client/src/ui/H2.tsx +0 -15
- package/templates/fate/cloudflare/client/src/ui/H3.tsx +0 -20
- package/templates/fate/cloudflare/client/src/ui/Header.tsx +0 -98
- package/templates/fate/cloudflare/client/src/ui/Input.tsx +0 -17
- package/templates/fate/cloudflare/client/src/ui/Link.tsx +0 -11
- package/templates/fate/cloudflare/client/src/ui/LocaleSwitcher.tsx +0 -34
- package/templates/fate/cloudflare/client/src/ui/PostCard.tsx +0 -303
- package/templates/fate/cloudflare/client/src/ui/Section.tsx +0 -23
- package/templates/fate/cloudflare/client/src/ui/UserCard.tsx +0 -131
- package/templates/fate/cloudflare/client/src/user/AuthClient.tsx +0 -6
- package/templates/fate/cloudflare/client/start.mjs +0 -11
- package/templates/fate/cloudflare/client/translations/de-DE.json +0 -632
- package/templates/fate/cloudflare/client/translations/ja-JP.json +0 -632
- package/templates/fate/cloudflare/client/vite.config.ts +0 -41
- package/templates/fate/cloudflare/client/void.json +0 -29
- package/templates/fate/cloudflare/package.json +0 -42
- package/templates/fate/cloudflare/pnpm-workspace.yaml +0 -41
- package/templates/fate/cloudflare/server/.env.example +0 -1
- package/templates/fate/cloudflare/server/db/db.ts +0 -30
- package/templates/fate/cloudflare/server/db/migrations/20260508073357_initial_schema.sql +0 -134
- package/templates/fate/cloudflare/server/db/migrations/20260508120500_seed_cloudflare_demo.sql +0 -170
- package/templates/fate/cloudflare/server/db/migrations/meta/_journal.json +0 -20
- package/templates/fate/cloudflare/server/db/queries.ts +0 -211
- package/templates/fate/cloudflare/server/db/schema.ts +0 -331
- package/templates/fate/cloudflare/server/node_modules/.bin/cf-wrangler +0 -22
- package/templates/fate/cloudflare/server/node_modules/.bin/drizzle-kit +0 -22
- package/templates/fate/cloudflare/server/node_modules/.bin/esbuild +0 -22
- package/templates/fate/cloudflare/server/node_modules/.bin/fate +0 -22
- package/templates/fate/cloudflare/server/node_modules/.bin/intent +0 -22
- package/templates/fate/cloudflare/server/node_modules/.bin/jiti +0 -22
- package/templates/fate/cloudflare/server/node_modules/.bin/oxfmt +0 -22
- package/templates/fate/cloudflare/server/node_modules/.bin/oxlint +0 -22
- package/templates/fate/cloudflare/server/node_modules/.bin/tsc +0 -22
- package/templates/fate/cloudflare/server/node_modules/.bin/tsx +0 -22
- package/templates/fate/cloudflare/server/node_modules/.bin/vitest +0 -22
- package/templates/fate/cloudflare/server/node_modules/.bin/vp +0 -22
- package/templates/fate/cloudflare/server/node_modules/.bin/vpr +0 -22
- package/templates/fate/cloudflare/server/node_modules/.bin/workerd +0 -22
- package/templates/fate/cloudflare/server/node_modules/.bin/wrangler +0 -22
- package/templates/fate/cloudflare/server/node_modules/.bin/wrangler2 +0 -22
- package/templates/fate/cloudflare/server/package.json +0 -34
- package/templates/fate/cloudflare/server/src/fate/commentPermissions.ts +0 -17
- package/templates/fate/cloudflare/server/src/fate/context.ts +0 -15
- package/templates/fate/cloudflare/server/src/fate/live.ts +0 -7
- package/templates/fate/cloudflare/server/src/fate/server.ts +0 -414
- package/templates/fate/cloudflare/server/src/fate/views.ts +0 -164
- package/templates/fate/cloudflare/server/src/index.ts +0 -80
- package/templates/fate/cloudflare/server/src/lib/auth.ts +0 -64
- package/templates/fate/cloudflare/server/src/lib/origins.ts +0 -17
- package/templates/fate/cloudflare/server/src/router.ts +0 -2
- package/templates/fate/cloudflare/server/src/user/SessionUser.tsx +0 -17
- package/templates/fate/cloudflare/server/tsconfig.json +0 -10
- package/templates/fate/cloudflare/server/wrangler.jsonc +0 -29
- package/templates/fate/cloudflare/tsconfig.json +0 -24
- package/templates/fate/cloudflare/vite.config.ts +0 -75
- package/templates/fate/graphql-client/.env.example +0 -3
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
/// <reference types="fbtee/ReactTypes.d.ts" />
|
|
2
|
-
|
|
3
|
-
import { createLocaleContext, useLocaleContext } from 'fbtee';
|
|
4
|
-
import { ReactNode, startTransition, useEffect, useState } from 'react';
|
|
5
|
-
import AvailableLanguages from './AvailableLanguages.tsx';
|
|
6
|
-
|
|
7
|
-
const Context = createLocaleContext({
|
|
8
|
-
availableLanguages: AvailableLanguages,
|
|
9
|
-
// Keep the server and first client render identical; restore preferences after hydration.
|
|
10
|
-
clientLocales: ['en-US'],
|
|
11
|
-
fallbackLocale: 'en-US',
|
|
12
|
-
loadLocale: async (locale) => {
|
|
13
|
-
if (locale !== 'en-US' && AvailableLanguages.has(locale)) {
|
|
14
|
-
return (await import(`../translations/${locale}.json`)).default[locale];
|
|
15
|
-
}
|
|
16
|
-
return {};
|
|
17
|
-
},
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
function RestoreLocale({ restored, onRestore }: { restored: boolean; onRestore: () => void }) {
|
|
21
|
-
const { locale, setLocale } = useLocaleContext();
|
|
22
|
-
useEffect(() => {
|
|
23
|
-
if (restored) return;
|
|
24
|
-
onRestore();
|
|
25
|
-
let savedLocale: string | null = null;
|
|
26
|
-
try {
|
|
27
|
-
savedLocale = localStorage.getItem('fbtee:locale');
|
|
28
|
-
} catch {
|
|
29
|
-
// Browser storage can be unavailable.
|
|
30
|
-
}
|
|
31
|
-
const preferred = [savedLocale, ...navigator.languages, navigator.language]
|
|
32
|
-
.filter((value): value is string => !!value)
|
|
33
|
-
.map((value) => value.replaceAll('_', '-'));
|
|
34
|
-
const nextLocale = preferred
|
|
35
|
-
.map((value) =>
|
|
36
|
-
AvailableLanguages.has(value)
|
|
37
|
-
? value
|
|
38
|
-
: [...AvailableLanguages.keys()].find(
|
|
39
|
-
(available) => available.split('-')[0] === value.split('-')[0],
|
|
40
|
-
),
|
|
41
|
-
)
|
|
42
|
-
.find(Boolean);
|
|
43
|
-
if (nextLocale && nextLocale !== locale) {
|
|
44
|
-
startTransition(() => setLocale(nextLocale));
|
|
45
|
-
}
|
|
46
|
-
}, [locale, setLocale, restored, onRestore]);
|
|
47
|
-
useEffect(() => {
|
|
48
|
-
document.documentElement.lang = locale;
|
|
49
|
-
}, [locale]);
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export default function LocaleContext({ children }: { children: ReactNode }) {
|
|
54
|
-
const [restored, setRestored] = useState(false);
|
|
55
|
-
return (
|
|
56
|
-
<Context>
|
|
57
|
-
<RestoreLocale restored={restored} onRestore={() => setRestored(true)} />
|
|
58
|
-
{children}
|
|
59
|
-
</Context>
|
|
60
|
-
);
|
|
61
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { cva, type VariantProps } from 'class-variance-authority';
|
|
2
|
-
import { HTMLAttributes } from 'react';
|
|
3
|
-
import cx from '../lib/cx.tsx';
|
|
4
|
-
|
|
5
|
-
const badgeVariants = cva(
|
|
6
|
-
'inline-flex items-center squircle border px-1.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
|
7
|
-
{
|
|
8
|
-
defaultVariants: {
|
|
9
|
-
variant: 'default',
|
|
10
|
-
},
|
|
11
|
-
variants: {
|
|
12
|
-
variant: {
|
|
13
|
-
default: 'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
|
|
14
|
-
destructive:
|
|
15
|
-
'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
|
|
16
|
-
outline: 'text-foreground',
|
|
17
|
-
secondary:
|
|
18
|
-
'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
|
19
|
-
},
|
|
20
|
-
},
|
|
21
|
-
},
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
export interface BadgeProps
|
|
25
|
-
extends HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {}
|
|
26
|
-
|
|
27
|
-
function Badge({ className, variant, ...props }: BadgeProps) {
|
|
28
|
-
return <div className={cx(badgeVariants({ variant }), className)} {...props} />;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export { Badge, badgeVariants };
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { Slot } from '@radix-ui/react-slot';
|
|
2
|
-
import { cva, type VariantProps } from 'class-variance-authority';
|
|
3
|
-
import { type ButtonHTMLAttributes, type ReactNode, useOptimistic, useTransition } from 'react';
|
|
4
|
-
import { useFormStatus } from 'react-dom';
|
|
5
|
-
import cx from '../lib/cx.tsx';
|
|
6
|
-
|
|
7
|
-
const buttonVariants = cva(
|
|
8
|
-
'inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap squircle text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
|
9
|
-
{
|
|
10
|
-
defaultVariants: {
|
|
11
|
-
size: 'default',
|
|
12
|
-
variant: 'default',
|
|
13
|
-
},
|
|
14
|
-
variants: {
|
|
15
|
-
size: {
|
|
16
|
-
default: 'h-10 px-3 py-2 active:pt-[11px] active:pb-[9px]',
|
|
17
|
-
icon: 'h-10 w-10',
|
|
18
|
-
lg: 'h-11 squircle px-6 active:pt-[11px] active:pb-[9px]',
|
|
19
|
-
sm: 'h-9 squircle px-2 active:pt-[11px] active:pb-[9px]',
|
|
20
|
-
},
|
|
21
|
-
variant: {
|
|
22
|
-
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
|
23
|
-
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
|
24
|
-
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
|
25
|
-
link: 'text-primary underline-offset-4 hover:underline',
|
|
26
|
-
outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
|
|
27
|
-
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
const Button = ({
|
|
34
|
-
action,
|
|
35
|
-
asChild = false,
|
|
36
|
-
className,
|
|
37
|
-
disabled,
|
|
38
|
-
onClick: initialOnClick,
|
|
39
|
-
size,
|
|
40
|
-
variant,
|
|
41
|
-
...props
|
|
42
|
-
}: ButtonHTMLAttributes<HTMLButtonElement> &
|
|
43
|
-
VariantProps<typeof buttonVariants> & {
|
|
44
|
-
action?: () => void;
|
|
45
|
-
asChild?: boolean;
|
|
46
|
-
}) => {
|
|
47
|
-
const Component = asChild ? Slot : 'button';
|
|
48
|
-
|
|
49
|
-
const [isPending, startTransition] = useTransition();
|
|
50
|
-
|
|
51
|
-
const onClick = initialOnClick || (action ? () => startTransition(action) : undefined);
|
|
52
|
-
|
|
53
|
-
return (
|
|
54
|
-
<Component
|
|
55
|
-
className={cx(buttonVariants({ className, size, variant }))}
|
|
56
|
-
disabled={disabled !== undefined ? disabled : isPending}
|
|
57
|
-
onClick={onClick}
|
|
58
|
-
{...props}
|
|
59
|
-
/>
|
|
60
|
-
);
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
const AsyncButton = ({
|
|
64
|
-
action,
|
|
65
|
-
asChild = false,
|
|
66
|
-
children,
|
|
67
|
-
className,
|
|
68
|
-
disabled,
|
|
69
|
-
pendingPlaceholder = '...',
|
|
70
|
-
size,
|
|
71
|
-
variant,
|
|
72
|
-
...props
|
|
73
|
-
}: ButtonHTMLAttributes<HTMLButtonElement> &
|
|
74
|
-
VariantProps<typeof buttonVariants> & {
|
|
75
|
-
action?: () => Promise<unknown> | void;
|
|
76
|
-
asChild?: boolean;
|
|
77
|
-
pendingPlaceholder?: ReactNode;
|
|
78
|
-
}) => {
|
|
79
|
-
const Component = asChild ? Slot : 'button';
|
|
80
|
-
|
|
81
|
-
const [optimisticIsPending, setOptimisticIsPending] = useOptimistic(false);
|
|
82
|
-
const [transitionIsPending, startTransition] = useTransition();
|
|
83
|
-
const { pending: formIsPending } = useFormStatus();
|
|
84
|
-
|
|
85
|
-
const isPending = transitionIsPending || optimisticIsPending || formIsPending;
|
|
86
|
-
|
|
87
|
-
const onClick = action
|
|
88
|
-
? () => {
|
|
89
|
-
startTransition(async () => {
|
|
90
|
-
setOptimisticIsPending(true);
|
|
91
|
-
await action();
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
: undefined;
|
|
95
|
-
|
|
96
|
-
return (
|
|
97
|
-
<Component
|
|
98
|
-
className={cx(buttonVariants({ className, size, variant }))}
|
|
99
|
-
disabled={disabled || isPending || undefined}
|
|
100
|
-
onClick={onClick}
|
|
101
|
-
{...props}
|
|
102
|
-
>
|
|
103
|
-
{isPending ? pendingPlaceholder : children}
|
|
104
|
-
</Component>
|
|
105
|
-
);
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
export { AsyncButton, Button, buttonVariants };
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { VStack } from '@nkzw/stack';
|
|
2
|
-
import { HTMLAttributes } from 'react';
|
|
3
|
-
import cx from '../lib/cx.tsx';
|
|
4
|
-
|
|
5
|
-
export default function Card({
|
|
6
|
-
children,
|
|
7
|
-
className,
|
|
8
|
-
...props
|
|
9
|
-
}: {
|
|
10
|
-
children: React.ReactNode;
|
|
11
|
-
className?: string;
|
|
12
|
-
} & HTMLAttributes<HTMLDivElement>) {
|
|
13
|
-
return (
|
|
14
|
-
<div
|
|
15
|
-
className={cx(
|
|
16
|
-
'relative overflow-hidden squircle border border-gray-200/70 bg-white/85 shadow-[0_16px_70px_-32px_rgba(15,23,42,0.45)] dark:border-neutral-800 dark:bg-neutral-900/80 transition duration-200 ease-out hover:shadow-[0_24px_80px_-38px_rgba(15,23,42,0.55)]',
|
|
17
|
-
className,
|
|
18
|
-
)}
|
|
19
|
-
{...props}
|
|
20
|
-
>
|
|
21
|
-
<div className="pointer-events-none absolute inset-0 bg-linear-to-br from-blue-500/10 via-transparent to-indigo-500/15 opacity-30 transition duration-200" />
|
|
22
|
-
<VStack
|
|
23
|
-
className="relative z-10 flex h-full w-full"
|
|
24
|
-
gap={16}
|
|
25
|
-
horizontalPadding={20}
|
|
26
|
-
verticalPadding={16}
|
|
27
|
-
>
|
|
28
|
-
{children}
|
|
29
|
-
</VStack>
|
|
30
|
-
</div>
|
|
31
|
-
);
|
|
32
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import type { Comment } from '@app/server/src/router.ts';
|
|
2
|
-
import Stack from '@nkzw/stack';
|
|
3
|
-
import { X } from 'lucide-react';
|
|
4
|
-
import { useView, view, ViewRef } from 'react-fate';
|
|
5
|
-
import { useFateClient } from 'react-fate';
|
|
6
|
-
import { Button } from './Button.tsx';
|
|
7
|
-
import Link from './Link.tsx';
|
|
8
|
-
|
|
9
|
-
export const CommentView = view<Comment>()({
|
|
10
|
-
author: {
|
|
11
|
-
id: true,
|
|
12
|
-
name: true,
|
|
13
|
-
username: true,
|
|
14
|
-
},
|
|
15
|
-
content: true,
|
|
16
|
-
id: true,
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
export const CommentViewWithPostCount = view<Comment>()({
|
|
20
|
-
...CommentView,
|
|
21
|
-
post: { commentCount: true },
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
export default function CommentCard({
|
|
25
|
-
comment: commentRef,
|
|
26
|
-
link,
|
|
27
|
-
post,
|
|
28
|
-
}: {
|
|
29
|
-
comment: ViewRef<'Comment'>;
|
|
30
|
-
link?: boolean;
|
|
31
|
-
post: { commentCount: number; id: string; title: string };
|
|
32
|
-
}) {
|
|
33
|
-
const fate = useFateClient();
|
|
34
|
-
const comment = useView(CommentView, commentRef);
|
|
35
|
-
const { author } = comment;
|
|
36
|
-
|
|
37
|
-
return (
|
|
38
|
-
<div
|
|
39
|
-
className="group squircle border border-gray-200/80 bg-gray-100/50 p-3 text-sm dark:border-neutral-800 dark:bg-neutral-900/40"
|
|
40
|
-
key={comment.id}
|
|
41
|
-
>
|
|
42
|
-
<Stack between gap={16}>
|
|
43
|
-
<p className="font-medium text-gray-900 dark:text-gray-200">
|
|
44
|
-
{author?.name ?? 'Anonymous'}
|
|
45
|
-
</p>
|
|
46
|
-
<Button
|
|
47
|
-
className="opacity-0 transition-opacity group-hover:opacity-100 focus:opacity-100"
|
|
48
|
-
onClick={async () => {
|
|
49
|
-
await fate.mutations.comment.delete({
|
|
50
|
-
delete: true,
|
|
51
|
-
input: { id: comment.id },
|
|
52
|
-
optimistic: {
|
|
53
|
-
post: { commentCount: post.commentCount - 1, id: post.id },
|
|
54
|
-
},
|
|
55
|
-
view: view<Comment>()({
|
|
56
|
-
id: true,
|
|
57
|
-
post: { commentCount: true },
|
|
58
|
-
}),
|
|
59
|
-
});
|
|
60
|
-
}}
|
|
61
|
-
size="sm"
|
|
62
|
-
variant="ghost"
|
|
63
|
-
>
|
|
64
|
-
<X size={14} />
|
|
65
|
-
</Button>
|
|
66
|
-
</Stack>
|
|
67
|
-
<p className="text-foreground/80">{comment.content}</p>
|
|
68
|
-
{link && (
|
|
69
|
-
<Link className="text-blue-500 underline hover:no-underline" to={`/post/${post.id}`}>
|
|
70
|
-
{post.title}
|
|
71
|
-
</Link>
|
|
72
|
-
)}
|
|
73
|
-
</div>
|
|
74
|
-
);
|
|
75
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { VStack } from '@nkzw/stack';
|
|
2
|
-
|
|
3
|
-
export default function Error({ error: initialError }: { error: unknown }) {
|
|
4
|
-
const error = initialError instanceof Error ? (initialError as Error) : null;
|
|
5
|
-
return (
|
|
6
|
-
<VStack gap={12}>
|
|
7
|
-
<h3 className="text-xl font-semibold text-red-700">Error</h3>
|
|
8
|
-
<code>{error ? error.stack || `fate Error: ${error.message}` : String(initialError)}</code>
|
|
9
|
-
<div>
|
|
10
|
-
<a
|
|
11
|
-
className="cursor-pointer underline decoration-gray-300 hover:no-underline"
|
|
12
|
-
onClick={() => window.location.reload()}
|
|
13
|
-
>
|
|
14
|
-
<fbt desc="Try again button">Try again</fbt>
|
|
15
|
-
</a>
|
|
16
|
-
</div>
|
|
17
|
-
</VStack>
|
|
18
|
-
);
|
|
19
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import cx from '../lib/cx.tsx';
|
|
2
|
-
|
|
3
|
-
export default function H2({
|
|
4
|
-
children,
|
|
5
|
-
className,
|
|
6
|
-
}: {
|
|
7
|
-
children: React.ReactNode;
|
|
8
|
-
className?: string;
|
|
9
|
-
}) {
|
|
10
|
-
return (
|
|
11
|
-
<h2 className={cx('text-2xl font-semibold text-gray-900 dark:text-gray-50', className)}>
|
|
12
|
-
{children}
|
|
13
|
-
</h2>
|
|
14
|
-
);
|
|
15
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import cx from '../lib/cx.tsx';
|
|
2
|
-
|
|
3
|
-
export default function H2({
|
|
4
|
-
children,
|
|
5
|
-
className,
|
|
6
|
-
}: {
|
|
7
|
-
children: React.ReactNode;
|
|
8
|
-
className?: string;
|
|
9
|
-
}) {
|
|
10
|
-
return (
|
|
11
|
-
<h3
|
|
12
|
-
className={cx(
|
|
13
|
-
'bg-linear-to-r from-slate-900 to-slate-600 bg-clip-text text-lg font-semibold text-transparent transition dark:from-white dark:to-slate-200 uppercase tracking-widest duration-150 opacity-100 hover:opacity-70 active:translate-y-[1.5px]',
|
|
14
|
-
className,
|
|
15
|
-
)}
|
|
16
|
-
>
|
|
17
|
-
{children}
|
|
18
|
-
</h3>
|
|
19
|
-
);
|
|
20
|
-
}
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import Stack from '@nkzw/stack';
|
|
2
|
-
import { useRouter } from '@void/react';
|
|
3
|
-
import { ChevronLeft, LogIn, LogOut, Search } from 'lucide-react';
|
|
4
|
-
import AuthClient from '../user/AuthClient.tsx';
|
|
5
|
-
import { Button } from './Button.tsx';
|
|
6
|
-
import Link from './Link.tsx';
|
|
7
|
-
import LocaleSwitcher from './LocaleSwitcher.tsx';
|
|
8
|
-
|
|
9
|
-
export default function Header() {
|
|
10
|
-
const router = useRouter();
|
|
11
|
-
const pathname = router.path;
|
|
12
|
-
const { data: session, isPending } = AuthClient.useSession();
|
|
13
|
-
|
|
14
|
-
return (
|
|
15
|
-
<header className="sticky top-0 z-50 border-b border-white/60 bg-white/60 backdrop-blur dark:border-neutral-800 dark:bg-neutral-950/70">
|
|
16
|
-
<Stack
|
|
17
|
-
alignCenter
|
|
18
|
-
between
|
|
19
|
-
className="max-w-8xl relative container mx-auto h-16 px-6 lg:px-8"
|
|
20
|
-
gap={16}
|
|
21
|
-
>
|
|
22
|
-
<Link
|
|
23
|
-
href="/"
|
|
24
|
-
onClick={() =>
|
|
25
|
-
pathname !== '/' && history.state?.idx > 0 ? history.back() : router.visit('/')
|
|
26
|
-
}
|
|
27
|
-
>
|
|
28
|
-
<Stack alignCenter gap={12}>
|
|
29
|
-
<div className="squircle relative flex h-10 w-10 items-center justify-center bg-linear-to-br from-gray-100 to-gray-200 text-white shadow-lg shadow-gray-500/20 transition duration-250 hover:scale-110 active:scale-90">
|
|
30
|
-
{pathname === '/' ? (
|
|
31
|
-
<svg
|
|
32
|
-
className="h-6 w-6 text-gray-500"
|
|
33
|
-
preserveAspectRatio="xMidYMax"
|
|
34
|
-
viewBox="0 0 160 160"
|
|
35
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
36
|
-
>
|
|
37
|
-
<path
|
|
38
|
-
d="M108.301 17.465h.272c1.797-.005 3.563.177 5.331.496l.349.063c5.588 1.02 10.731 3.447 15.201 6.933l.347.267c1.579 1.26 3.083 2.684 4.386 4.227l.161.186c4.018 4.719 6.924 10.686 7.767 16.852l.033.239c.262 1.848.351 3.688.352 5.551l.001.307c-.001 1.623-.092 3.188-.386 4.788l-.05.292c-1.003 5.874-3.476 11.447-7.286 16.034l-.29.358c-1.422 1.721-3.011 3.382-4.797 4.729l-.444.354c-3.046 2.405-6.436 4.147-10.084 5.442l-.497.186c-1.766.627-3.615.974-5.465 1.266l-.364.058c-1.591.238-3.185.286-4.791.281h-.499l-1.339-.002h-1.408c-.886-.001-1.772-.001-2.656-.003-3.085-.001-6.163-.005-9.247-.008v-12.78l3.606-.006 2.282-.007c1.205-.006 2.407-.01 3.611-.011.878 0 1.754-.003 2.63-.007.462-.002.928-.005 1.389-.002 6.302.01 12.003-1.72 16.65-6.117l.291-.272c3.951-3.807 6.097-9.302 6.241-14.736.04-3.078-.476-6.048-1.68-8.892l-.104-.244c-2.382-5.559-6.98-9.439-12.504-11.675-2.204-.816-4.522-1.16-6.863-1.134-.301.004-.606.004-.909.001-1.677.003-3.304.127-4.924.591l-.331.098c-2.625.758-5.093 1.928-7.254 3.607l-.323.249c-1.046.817-2.031 1.682-2.873 2.71l-.154.179c-2.354 2.782-3.9 5.988-4.727 9.529l-.054.229c-.391 1.8-.423 3.633-.42 5.471l-.001.74c.002 1.4.001 2.801-.004 4.201l-.004 3.779c0 1.824-.001 3.649-.003 5.473l-.006 2.813-.005 6.076-.001.267-.008 8.614c-.001 2.023-.004 4.045-.006 6.066v.259l-.004 2.55-.006 5.46c0 1.331-.001 2.658-.003 3.985-.002.72-.002 1.438-.002 2.156 0 .658 0 1.317-.002 1.978v.708c.004 2.098-.07 4.214-.545 6.263l-.077.359-.161.716c-.071.302-.136.605-.199.909-.55 2.538-1.6 5.067-2.832 7.348l-.214.42c-.965 1.921-2.22 3.714-3.574 5.38l-.152.183c-.78.973-1.639 1.866-2.519 2.751l-.308.313a25.81 25.81 0 0 1-2.226 1.955l-.22.179c-3.698 2.953-7.975 5.044-12.538 6.262-.212.056-.422.115-.631.176-8.366 2.287-17.8.738-25.278-3.512-3.639-2.122-7.171-4.945-9.721-8.307-.121-.15-.241-.298-.356-.448a36.247 36.247 0 0 1-2.605-3.814l-.144-.234c-.889-1.466-1.562-3.034-2.222-4.617l-.11-.265c-3.253-7.791-2.911-17.367.184-25.133.865-2.069 1.869-4.089 3.123-5.951l.172-.261a31.91 31.91 0 0 1 2.547-3.29l.229-.263c.13-.152.266-.304.396-.452l.425-.493c1.216-1.389 2.593-2.615 4.068-3.723.208-.157.414-.315.62-.476 1.184-.901 2.437-1.665 3.729-2.4l.272-.159c1.586-.893 3.235-1.573 4.934-2.21l.253-.097c4.232-1.567 8.615-1.836 13.079-1.82 1.359 0 2.732.002 4.093.007 1.209.005 2.417.006 3.621.006 1.899.002 3.794.007 5.688.012v12.78l-3.612.013-2.282.011c-1.201.005-2.401.012-3.61.016-.876.003-1.751.005-2.629.013-.463.002-.924.005-1.387.006-4.322.005-8.193.592-12.033 2.662l-.217.114c-1.458.773-2.697 1.731-3.908 2.849-.206.185-.413.362-.63.535-.435.376-.794.788-1.16 1.232l-.153.18a21.18 21.18 0 0 0-4.474 8.651l-.094.342c-.42 1.682-.54 3.395-.536 5.127v.272c.019 5.672 2.392 10.671 6.206 14.769l.215.237c3.296 3.453 8.483 5.917 13.247 6.046.448.008.896.01 1.345.01.276 0 .553.003.835.005 4.917.019 9.864-1.73 13.577-4.995l.303-.267c.299-.263.592-.527.881-.799l.262-.247c.787-.756 1.451-1.599 2.104-2.474l.151-.201c1.929-2.606 3.117-5.723 3.635-8.909l.059-.362c.194-1.296.21-2.573.207-3.883l.003-.74v-2.02c0-.73 0-1.459.005-2.188v-3.782c0-1.827.004-3.654.007-5.481 0-.939 0-1.877.004-2.816l.003-6.082v-.268c.005-2.876.008-5.75.008-8.627l.013-8.883c.004-1.823.004-3.646.004-5.469 0-1.33.004-2.659.004-3.989.003-.721.003-1.442.003-2.159v-1.98c.005-.237.005-.473 0-.711-.011-8.523 3.072-17.023 8.941-23.268.244-.263.483-.531.717-.805.416-.48.861-.918 1.324-1.352l.252-.232a26.259 26.259 0 0 1 2.184-1.812l.46-.349c1.39-1.058 2.837-1.951 4.375-2.775l.318-.172c5.005-2.666 10.5-3.877 16.15-3.889Z"
|
|
39
|
-
fill="currentColor"
|
|
40
|
-
style={{ transformBox: 'fill-box', transformOrigin: '50% 50%' }}
|
|
41
|
-
transform="rotate(-31 0 0)"
|
|
42
|
-
/>
|
|
43
|
-
</svg>
|
|
44
|
-
) : (
|
|
45
|
-
<ChevronLeft className="h-6 w-6 text-gray-500" />
|
|
46
|
-
)}
|
|
47
|
-
</div>
|
|
48
|
-
<div className="opacity-100 transition-opacity duration-250 hover:opacity-70">
|
|
49
|
-
<span className="bg-linear-to-r from-gray-500 to-gray-900 bg-clip-text text-xl font-semibold text-transparent dark:from-gray-200 dark:to-white">
|
|
50
|
-
<span className="italic">fate</span>
|
|
51
|
-
</span>
|
|
52
|
-
<p className="hidden text-xs text-muted-foreground sm:block">
|
|
53
|
-
<fbt desc="fate library tagline">A modern data client for React.</fbt>
|
|
54
|
-
</p>
|
|
55
|
-
</div>
|
|
56
|
-
</Stack>
|
|
57
|
-
</Link>
|
|
58
|
-
<Stack alignCenter>
|
|
59
|
-
<LocaleSwitcher />
|
|
60
|
-
<Button asChild size="sm" variant="ghost">
|
|
61
|
-
<Link to="/search">
|
|
62
|
-
<Search className="h-4 w-4" />
|
|
63
|
-
<span className="hidden sm:inline">
|
|
64
|
-
<fbt desc="Search button">Search</fbt>
|
|
65
|
-
</span>
|
|
66
|
-
</Link>
|
|
67
|
-
</Button>
|
|
68
|
-
<div className="flex w-9 justify-end sm:w-24">
|
|
69
|
-
{session ? (
|
|
70
|
-
<Button asChild className="w-full" size="sm" variant="ghost">
|
|
71
|
-
<Stack
|
|
72
|
-
alignCenter
|
|
73
|
-
as="a"
|
|
74
|
-
className="text-sm font-medium text-gray-700 transition hover:text-gray-900 dark:text-gray-200 dark:hover:text-white"
|
|
75
|
-
gap={4}
|
|
76
|
-
onClick={() => AuthClient.signOut()}
|
|
77
|
-
>
|
|
78
|
-
<LogOut className="h-4 w-4" />
|
|
79
|
-
<span className="hidden sm:inline">
|
|
80
|
-
<fbt desc="Logout button">Logout</fbt>
|
|
81
|
-
</span>
|
|
82
|
-
</Stack>
|
|
83
|
-
</Button>
|
|
84
|
-
) : !isPending ? (
|
|
85
|
-
<Button asChild className="w-full" size="sm" variant="ghost">
|
|
86
|
-
<Link to="/login">
|
|
87
|
-
<LogIn className="h-4 w-4" /> <fbt desc="Login button">Login</fbt>
|
|
88
|
-
</Link>
|
|
89
|
-
</Button>
|
|
90
|
-
) : (
|
|
91
|
-
<div aria-hidden className="h-9 w-full" />
|
|
92
|
-
)}
|
|
93
|
-
</div>
|
|
94
|
-
</Stack>
|
|
95
|
-
</Stack>
|
|
96
|
-
</header>
|
|
97
|
-
);
|
|
98
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { InputHTMLAttributes, Ref } from 'react';
|
|
2
|
-
import cx from '../lib/cx.tsx';
|
|
3
|
-
|
|
4
|
-
export default function Input({
|
|
5
|
-
className,
|
|
6
|
-
...props
|
|
7
|
-
}: InputHTMLAttributes<HTMLInputElement> & { ref?: Ref<HTMLInputElement> }) {
|
|
8
|
-
return (
|
|
9
|
-
<input
|
|
10
|
-
className={cx(
|
|
11
|
-
'border-input bg-background text-foreground focus-visible:ring-ring focus-visible:ring-offset-background flex w-32 squircle border px-3 py-2 text-sm shadow-sm transition-colors focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 dark:bg-neutral-900/40',
|
|
12
|
-
className,
|
|
13
|
-
)}
|
|
14
|
-
{...props}
|
|
15
|
-
/>
|
|
16
|
-
);
|
|
17
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { Link as VoidLink } from '@void/react';
|
|
2
|
-
import type { ComponentProps } from 'react';
|
|
3
|
-
|
|
4
|
-
export type LinkProps = Omit<ComponentProps<typeof VoidLink>, 'href'> & {
|
|
5
|
-
href?: string;
|
|
6
|
-
to?: string;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
export default function Link({ href, to, ...props }: LinkProps) {
|
|
10
|
-
return <VoidLink href={href ?? to ?? '/'} {...props} />;
|
|
11
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { useLocaleContext } from 'fbtee';
|
|
2
|
-
import { startTransition } from 'react';
|
|
3
|
-
import AvailableLanguages from '../lib/AvailableLanguages.tsx';
|
|
4
|
-
|
|
5
|
-
export default function LocaleSwitcher() {
|
|
6
|
-
const { locale, localeChangeIsPending, setLocale } = useLocaleContext();
|
|
7
|
-
return (
|
|
8
|
-
<label className="inline-flex items-center gap-2 text-sm">
|
|
9
|
-
<span className="sr-only">
|
|
10
|
-
<fbt desc="Locale switcher button">Change Language</fbt>
|
|
11
|
-
</span>
|
|
12
|
-
<select
|
|
13
|
-
className="rounded-md border border-gray-300 bg-background px-2 py-1 text-foreground"
|
|
14
|
-
disabled={localeChangeIsPending}
|
|
15
|
-
onChange={(event) => {
|
|
16
|
-
const nextLocale = event.target.value;
|
|
17
|
-
try {
|
|
18
|
-
localStorage.setItem('fbtee:locale', nextLocale);
|
|
19
|
-
} catch {
|
|
20
|
-
// Changing the language still works without browser storage.
|
|
21
|
-
}
|
|
22
|
-
startTransition(() => setLocale(nextLocale));
|
|
23
|
-
}}
|
|
24
|
-
value={locale}
|
|
25
|
-
>
|
|
26
|
-
{[...AvailableLanguages].map(([value, name]) => (
|
|
27
|
-
<option key={value} value={value}>
|
|
28
|
-
{name}
|
|
29
|
-
</option>
|
|
30
|
-
))}
|
|
31
|
-
</select>
|
|
32
|
-
</label>
|
|
33
|
-
);
|
|
34
|
-
}
|