feat(FN-188): add Changelog tab to dashboard settings
Some checks failed
Sync dev → Gitea / Mirror dev to Gitea (push) Has been cancelled
Some checks failed
Sync dev → Gitea / Mirror dev to Gitea (push) Has been cancelled
Squash-recovery of fusion/fn-188 onto dev — original branch was based on
main (dccd4fa) due to baseBranch=None drift in Fusion settings; rebase
onto dev surfaced unrelated main-only commits (EMEX/PL24/Corgi) as false
conflicts. This commit applies only FN-188's 23-file changelog patch.
- apps/api: ChangelogModule (controller, service, DTO, spec, schema)
- apps/web: ChangelogTab (timeline + accordion), useChangelog hook, i18n
- packages/shared: changelog Zod schemas + types
- packages/ui: Accordion component + Badge stage variant
- docs/INDEX.md: changelog feature documented
Lint, typecheck, all 169 api tests + 2 web tests pass.
This commit is contained in:
@@ -29,6 +29,18 @@ export {
|
||||
forgotPasswordSchema,
|
||||
resetPasswordSchema,
|
||||
} from "./schemas/auth.js";
|
||||
export {
|
||||
changelogStageEnum,
|
||||
changelogEntrySchema,
|
||||
createChangelogEntrySchema,
|
||||
updateChangelogEntrySchema,
|
||||
} from "./schemas/changelog.js";
|
||||
export type {
|
||||
ChangelogEntry,
|
||||
CreateChangelogEntry,
|
||||
UpdateChangelogEntry,
|
||||
ChangelogStage,
|
||||
} from "./schemas/changelog.js";
|
||||
export { paginationSchema } from "./schemas/pagination.js";
|
||||
|
||||
// Constants
|
||||
|
||||
27
packages/shared/src/schemas/changelog.ts
Normal file
27
packages/shared/src/schemas/changelog.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const changelogStageEnum = z.enum(["alpha", "beta", "prod"]);
|
||||
|
||||
export const changelogEntrySchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
stage: changelogStageEnum,
|
||||
title: z.string().min(1).max(255),
|
||||
description: z.string().min(1),
|
||||
publishedAt: z.string().datetime(),
|
||||
createdAt: z.string().datetime(),
|
||||
updatedAt: z.string().datetime(),
|
||||
});
|
||||
|
||||
export const createChangelogEntrySchema = z.object({
|
||||
stage: changelogStageEnum,
|
||||
title: z.string().min(1).max(255),
|
||||
description: z.string().min(1),
|
||||
publishedAt: z.string().datetime(),
|
||||
});
|
||||
|
||||
export const updateChangelogEntrySchema = createChangelogEntrySchema.partial();
|
||||
|
||||
export type ChangelogEntry = z.infer<typeof changelogEntrySchema>;
|
||||
export type CreateChangelogEntry = z.infer<typeof createChangelogEntrySchema>;
|
||||
export type UpdateChangelogEntry = z.infer<typeof updateChangelogEntrySchema>;
|
||||
export type ChangelogStage = z.infer<typeof changelogStageEnum>;
|
||||
6
packages/shared/src/types/changelog.ts
Normal file
6
packages/shared/src/types/changelog.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export type {
|
||||
ChangelogEntry,
|
||||
CreateChangelogEntry,
|
||||
UpdateChangelogEntry,
|
||||
ChangelogStage,
|
||||
} from "../schemas/changelog.js";
|
||||
@@ -15,6 +15,7 @@
|
||||
"clean": "rm -rf dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-accordion": "^1.2.12",
|
||||
"@radix-ui/react-dialog": "^1.1.0",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.0",
|
||||
"@radix-ui/react-label": "^2.1.0",
|
||||
|
||||
50
packages/ui/src/accordion.tsx
Normal file
50
packages/ui/src/accordion.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import * as AccordionPrimitive from "@radix-ui/react-accordion";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { cn } from "./utils";
|
||||
|
||||
const Accordion = AccordionPrimitive.Root;
|
||||
|
||||
const AccordionItem = React.forwardRef<
|
||||
React.ComponentRef<typeof AccordionPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AccordionPrimitive.Item ref={ref} className={cn("border-b", className)} {...props} />
|
||||
));
|
||||
AccordionItem.displayName = "AccordionItem";
|
||||
|
||||
const AccordionTrigger = React.forwardRef<
|
||||
React.ComponentRef<typeof AccordionPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<AccordionPrimitive.Header className="flex">
|
||||
<AccordionPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200" />
|
||||
</AccordionPrimitive.Trigger>
|
||||
</AccordionPrimitive.Header>
|
||||
));
|
||||
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
|
||||
|
||||
const AccordionContent = React.forwardRef<
|
||||
React.ComponentRef<typeof AccordionPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<AccordionPrimitive.Content
|
||||
ref={ref}
|
||||
className="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
|
||||
{...props}
|
||||
>
|
||||
<div className={cn("pb-4 pt-0", className)}>{children}</div>
|
||||
</AccordionPrimitive.Content>
|
||||
));
|
||||
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
|
||||
|
||||
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };
|
||||
@@ -12,6 +12,11 @@ const badgeVariants = cva(
|
||||
destructive: "border-transparent bg-destructive text-destructive-foreground shadow",
|
||||
outline: "text-foreground",
|
||||
},
|
||||
stage: {
|
||||
alpha: "border-transparent bg-amber-500/15 text-amber-600 dark:bg-amber-500/20 dark:text-amber-400",
|
||||
beta: "border-transparent bg-blue-500/15 text-blue-600 dark:bg-blue-500/20 dark:text-blue-400",
|
||||
prod: "border-transparent bg-emerald-500/15 text-emerald-600 dark:bg-emerald-500/20 dark:text-emerald-400",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
@@ -23,8 +28,8 @@ export interface BadgeProps
|
||||
extends React.HTMLAttributes<HTMLDivElement>,
|
||||
VariantProps<typeof badgeVariants> {}
|
||||
|
||||
function Badge({ className, variant, ...props }: BadgeProps) {
|
||||
return <div className={cn(badgeVariants({ variant }), className)} {...props} />;
|
||||
function Badge({ className, variant, stage, ...props }: BadgeProps) {
|
||||
return <div className={cn(badgeVariants({ variant, stage }), className)} {...props} />;
|
||||
}
|
||||
|
||||
export { Badge, badgeVariants };
|
||||
|
||||
@@ -14,6 +14,7 @@ export { Skeleton } from "./skeleton";
|
||||
export { Label } from "./label";
|
||||
export { Separator } from "./separator";
|
||||
export { Tabs, TabsList, TabsTrigger, TabsContent } from "./tabs";
|
||||
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "./accordion";
|
||||
export {
|
||||
Dialog,
|
||||
DialogPortal,
|
||||
|
||||
Reference in New Issue
Block a user