doo-boilerplate 0.1.10 → 0.1.12

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doo-boilerplate",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "CLI to scaffold Pila portal frontend projects",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,10 +1,13 @@
1
1
  'use client'
2
2
 
3
+ import { useState } from 'react'
3
4
  import { LogOut, Menu, User } from 'lucide-react'
4
5
  import Link from 'next/link'
6
+ import { useRouter } from 'next/navigation'
5
7
 
6
8
  import { useSignOut } from '@/features/auth/hooks/use-auth'
7
9
  import { useAuthStore } from '@/stores/auth-store'
10
+ import { ThemeToggle } from '@/components/common/theme-toggle'
8
11
 
9
12
  import { Avatar, AvatarFallback } from '../ui/avatar'
10
13
  import { Button } from '../ui/button'
@@ -18,6 +21,49 @@ import {
18
21
  } from '../ui/dropdown-menu'
19
22
  import { useSidebarStore } from './sidebar'
20
23
 
24
+ const LANGUAGES = [
25
+ { code: 'en', label: 'EN' },
26
+ { code: 'vi', label: 'VI' },
27
+ ]
28
+
29
+ function getLangFromCookie(): string {
30
+ if (typeof document === 'undefined') return 'en'
31
+ const match = document.cookie.match(/(?:^|;\s*)NEXT_LOCALE=([^;]+)/)
32
+ return match?.[1] ?? 'en'
33
+ }
34
+
35
+ function LangSwitcher() {
36
+ const [locale, setLocale] = useState(getLangFromCookie)
37
+ const router = useRouter()
38
+
39
+ function switchLocale(code: string) {
40
+ document.cookie = `NEXT_LOCALE=${code}; path=/; max-age=${365 * 24 * 60 * 60}`
41
+ setLocale(code)
42
+ router.refresh()
43
+ }
44
+
45
+ return (
46
+ <DropdownMenu>
47
+ <DropdownMenuTrigger asChild>
48
+ <Button variant="ghost" size="sm" className="w-10 px-0 font-medium">
49
+ {locale.toUpperCase().slice(0, 2)}
50
+ </Button>
51
+ </DropdownMenuTrigger>
52
+ <DropdownMenuContent align="end">
53
+ {LANGUAGES.map(({ code, label }) => (
54
+ <DropdownMenuItem
55
+ key={code}
56
+ onClick={() => switchLocale(code)}
57
+ className={locale === code ? 'font-semibold' : ''}
58
+ >
59
+ {label}
60
+ </DropdownMenuItem>
61
+ ))}
62
+ </DropdownMenuContent>
63
+ </DropdownMenu>
64
+ )
65
+ }
66
+
21
67
  interface HeaderProps {
22
68
  title?: string
23
69
  }
@@ -52,6 +98,11 @@ export function Header({ title = 'Dashboard' }: HeaderProps) {
52
98
  <h2 className="text-lg font-semibold">{title}</h2>
53
99
  </div>
54
100
 
101
+ {/* Controls */}
102
+ <div className="flex items-center gap-1">
103
+ <LangSwitcher />
104
+ <ThemeToggle />
105
+
55
106
  {/* User dropdown */}
56
107
  <DropdownMenu>
57
108
  <DropdownMenuTrigger asChild>
@@ -86,6 +137,7 @@ export function Header({ title = 'Dashboard' }: HeaderProps) {
86
137
  </DropdownMenuItem>
87
138
  </DropdownMenuContent>
88
139
  </DropdownMenu>
140
+ </div>
89
141
  </header>
90
142
  )
91
143
  }
@@ -1,8 +1,11 @@
1
1
  import { LogOut, User } from 'lucide-react'
2
+ import { useTranslation } from 'react-i18next'
2
3
 
3
4
  import { useSignOut } from '@/features/auth/hooks/use-auth'
4
5
  import { useAuthStore } from '@/stores/auth-store'
6
+ import { ThemeToggle } from '@/components/common/theme-toggle'
5
7
  import { Avatar, AvatarFallback } from '@/components/ui/avatar'
8
+ import { Button } from '@/components/ui/button'
6
9
  import {
7
10
  DropdownMenu,
8
11
  DropdownMenuContent,
@@ -11,7 +14,35 @@ import {
11
14
  DropdownMenuSeparator,
12
15
  DropdownMenuTrigger,
13
16
  } from '@/components/ui/dropdown-menu'
14
- import { Button } from '@/components/ui/button'
17
+
18
+ const LANGUAGES = [
19
+ { code: 'en', label: 'EN' },
20
+ { code: 'vi', label: 'VI' },
21
+ ]
22
+
23
+ function LangSwitcher() {
24
+ const { i18n } = useTranslation()
25
+ return (
26
+ <DropdownMenu>
27
+ <DropdownMenuTrigger asChild>
28
+ <Button variant='ghost' size='sm' className='w-10 px-0 font-medium'>
29
+ {i18n.language.toUpperCase().slice(0, 2)}
30
+ </Button>
31
+ </DropdownMenuTrigger>
32
+ <DropdownMenuContent align='end'>
33
+ {LANGUAGES.map(({ code, label }) => (
34
+ <DropdownMenuItem
35
+ key={code}
36
+ onClick={() => i18n.changeLanguage(code)}
37
+ className={i18n.language === code ? 'font-semibold' : ''}
38
+ >
39
+ {label}
40
+ </DropdownMenuItem>
41
+ ))}
42
+ </DropdownMenuContent>
43
+ </DropdownMenu>
44
+ )
45
+ }
15
46
 
16
47
  export function Header() {
17
48
  const { user } = useAuthStore()
@@ -25,9 +56,12 @@ export function Header() {
25
56
  .slice(0, 2)
26
57
 
27
58
  return (
28
- <header className='flex h-14 items-center border-b bg-background px-4 gap-4'>
59
+ <header className='flex h-14 items-center border-b bg-background px-4 gap-2'>
29
60
  <div className='flex-1' />
30
61
 
62
+ <LangSwitcher />
63
+ <ThemeToggle />
64
+
31
65
  <DropdownMenu>
32
66
  <DropdownMenuTrigger asChild>
33
67
  <Button variant='ghost' size='icon' className='rounded-full' aria-label='User menu'>