Sfoglia il codice sorgente

feature: add release notes dialog (#7)

* feature: add release notes dialog

* feature: parse release note date

* feat: switch to dayjs

* feat(locales): add release notes in English and French with new entries for UI improvements and release notes dialog
refactor(release-notes): update ReleaseNote model to use title and content keys for localization
feat(release-notes-dialog): implement localized rendering of release notes with date formatting
feat(date-utils): create date utility functions to handle locale-specific date formatting and parsing

---------

Co-authored-by: Mathias <mathiasnouzy@gmail.com>
Lucas Neves Pereira 1 mese fa
parent
commit
1af089f49c

+ 17 - 0
locales/en.ts

@@ -9,6 +9,23 @@ export default {
   error: {
     invalid_credentials: "Invalid credentials or account does not exist",
   },
+
+  // Release Notes
+  release_notes: {
+    title: "What's New",
+    release_notes: "Release Notes",
+    notes: {
+      note_2024_06_01: {
+        title: "🎉 New: Release Notes Dialog",
+        content: "You can now view what's new directly from the header! Stay tuned for more updates.",
+      },
+      note_2024_05_20: {
+        title: "UI Improvements",
+        content: "Improved mobile responsiveness and added subtle hover effects to buttons.",
+      },
+    },
+  },
+
   // Contact Support
   contact_support: "Contact Support",
   contact_support_subtitle: "Describe your issue and we'll help you as soon as possible. You can also write to us directly at",

+ 17 - 0
locales/fr.ts

@@ -9,6 +9,23 @@ export default {
   error: {
     invalid_credentials: "Identifiants invalides ou compte inexistant",
   },
+
+  // Release Notes
+  release_notes: {
+    title: "Nouveautés",
+    release_notes: "Notes",
+    notes: {
+      note_2024_06_01: {
+        title: "🎉 Nouveau : Dialogue des notes de version",
+        content: "Vous pouvez maintenant voir les nouveautés directement depuis l'en-tête ! Restez à l'écoute pour plus de mises à jour.",
+      },
+      note_2024_05_20: {
+        title: "Améliorations de l'interface",
+        content: "Amélioration de la réactivité mobile et ajout d'effets de survol subtils aux boutons.",
+      },
+    },
+  },
+
   // Contact Support
   contact_support: "Contacter le support",
   contact_support_subtitle: "Décrivez votre problème et nous vous aiderons dès que possible. Vous pouvez aussi nous écrire directement à",

+ 1 - 0
package.json

@@ -71,6 +71,7 @@
     "class-variance-authority": "^0.7.1",
     "clsx": "^2.1.1",
     "csv-parser": "^3.2.0",
+    "dayjs": "^1.11.13",
     "embla-carousel-auto-scroll": "^8.6.0",
     "embla-carousel-react": "^8.6.0",
     "eslint-config-prettier": "^10.1.1",

+ 8 - 0
pnpm-lock.yaml

@@ -167,6 +167,9 @@ importers:
       csv-parser:
         specifier: ^3.2.0
         version: 3.2.0
+      dayjs:
+        specifier: ^1.11.13
+        version: 1.11.13
       embla-carousel-auto-scroll:
         specifier: ^8.6.0
         version: 8.6.0(embla-carousel@8.6.0)
@@ -2939,6 +2942,9 @@ packages:
     resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
     engines: {node: '>= 0.4'}
 
+  dayjs@1.11.13:
+    resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
+
   debug@3.2.7:
     resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
     peerDependencies:
@@ -8152,6 +8158,8 @@ snapshots:
       es-errors: 1.3.0
       is-data-view: 1.0.2
 
+  dayjs@1.11.13: {}
+
   debug@3.2.7:
     dependencies:
       ms: 2.1.3

+ 4 - 0
src/features/layout/Header.tsx

@@ -8,6 +8,8 @@ import Logo from "@public/logo.png";
 import { useLogout } from "@/features/auth/model/useLogout";
 import { useSession } from "@/features/auth/lib/auth-client";
 import { Link } from "@/components/ui/link";
+import { ReleaseNotesDialog } from "@/features/release-notes";
+
 
 export const Header = () => {
   const session = useSession();
@@ -35,6 +37,8 @@ export const Header = () => {
 
       {/* User Menu */}
       <div className="navbar-end">
+        <ReleaseNotesDialog />
+        
         <div className="dropdown dropdown-end">
           <div className="btn btn-ghost btn-circle avatar" role="button" tabIndex={0}>
             <div className="w-8 rounded-full bg-primary text-primary-content flex items-center justify-center text-sm font-medium">

+ 1 - 0
src/features/release-notes/index.ts

@@ -0,0 +1 @@
+export { ReleaseNotesDialog } from "./ui/release-notes-dialog";

+ 18 - 0
src/features/release-notes/model/notes.ts

@@ -0,0 +1,18 @@
+export interface ReleaseNote {
+  date: string;
+  titleKey: string;
+  contentKey: string;
+}
+
+export const releaseNotes: ReleaseNote[] = [
+  {
+    date: "2024-06-01",
+    titleKey: "release_notes.notes.note_2024_06_01.title",
+    contentKey: "release_notes.notes.note_2024_06_01.content",
+  },
+  {
+    date: "2024-05-20",
+    titleKey: "release_notes.notes.note_2024_05_20.title",
+    contentKey: "release_notes.notes.note_2024_05_20.content",
+  },
+];

+ 41 - 0
src/features/release-notes/ui/release-notes-dialog.tsx

@@ -0,0 +1,41 @@
+"use client";
+
+import * as React from "react";
+import { Bell } from "lucide-react";
+
+import { useCurrentLocale, useI18n } from "locales/client";
+import { formatDate } from "@/shared/lib/date";
+import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
+import { Button } from "@/components/ui/button";
+
+import { releaseNotes } from "../model/notes";
+
+export function ReleaseNotesDialog() {
+  const t = useI18n();
+  const locale = useCurrentLocale();
+
+  return (
+    <Dialog>
+      <DialogTrigger asChild>
+        <Button aria-label={t("release_notes.release_notes")} size="icon" variant="ghost">
+          <span className="sr-only">{t("release_notes.release_notes")}</span>
+          <Bell className="h-4 w-4" />
+        </Button>
+      </DialogTrigger>
+      <DialogContent className="max-w-md">
+        <DialogHeader>
+          <DialogTitle>{t("release_notes.title")}</DialogTitle>
+        </DialogHeader>
+        <div className="space-y-4">
+          {releaseNotes.map((note) => (
+            <div className="border-b pb-2 last:border-b-0 last:pb-0" key={note.date}>
+              <div className="text-xs text-muted-foreground">{formatDate(note.date, locale)}</div>
+              <div className="font-semibold">{t(note.titleKey as keyof typeof t)}</div>
+              <div className="text-sm">{t(note.contentKey as keyof typeof t)}</div>
+            </div>
+          ))}
+        </div>
+      </DialogContent>
+    </Dialog>
+  );
+}

+ 36 - 0
src/shared/lib/date.ts

@@ -0,0 +1,36 @@
+import dayjs from "dayjs";
+import "dayjs/locale/fr";
+import "dayjs/locale/en";
+
+/**
+ * Default date formats for different locales
+ */
+const DEFAULT_FORMATS = {
+  en: "MMMM D, YYYY", // January 15, 2024
+  fr: "D MMMM YYYY", // 15 janvier 2024
+} as const;
+
+/**
+ * Date utility abstraction that properly handles locales
+ * Abstracts dayjs usage according to FSD architecture
+ */
+export const formatDate = (date: string | Date, locale: string = "en", format?: string): string => {
+  const defaultFormat = DEFAULT_FORMATS[locale as keyof typeof DEFAULT_FORMATS] || DEFAULT_FORMATS.en;
+  return dayjs(date)
+    .locale(locale)
+    .format(format || defaultFormat);
+};
+
+/**
+ * Get current date in specified locale
+ */
+export const getCurrentDate = (locale: string = "en"): dayjs.Dayjs => {
+  return dayjs().locale(locale);
+};
+
+/**
+ * Parse date and set locale
+ */
+export const parseDate = (date: string | Date, locale: string = "en"): dayjs.Dayjs => {
+  return dayjs(date).locale(locale);
+};