File size: 8,228 Bytes
ec17af3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
"use client";

import type { ReactNode } from "react";
import Link from "next/link";
import { usePathname, useRouter } from "next/navigation";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { useAuthStore } from "@/store/useAuthStore";

type NavItem = {
  href: string;
  label: string;
  shortLabel: string;
  icon: (active: boolean) => ReactNode;
};

const NAV_ITEMS: NavItem[] = [
  {
    href: "/",
    label: "Command Center",
    shortLabel: "Home",
    icon: (active) => (
      <svg className={cn("h-5 w-5", active ? "text-ember" : "text-ink/65")} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
        <path d="M3 10.5L12 3l9 7.5V21a1 1 0 0 1-1 1h-5v-6h-6v6H4a1 1 0 0 1-1-1v-10.5z" strokeLinecap="round" strokeLinejoin="round" />
      </svg>
    ),
  },
  {
    href: "/curriculum",
    label: "30-Day Plan",
    shortLabel: "30-Day",
    icon: (active) => (
      <svg className={cn("h-5 w-5", active ? "text-ember" : "text-ink/65")} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
        <rect x="3" y="4" width="18" height="17" rx="3" />
        <path d="M8 2v4M16 2v4M3 9h18M8 14l2.5 2.5L16 11" strokeLinecap="round" strokeLinejoin="round" />
      </svg>
    ),
  },
  {
    href: "/record",
    label: "Camera Studio",
    shortLabel: "Studio",
    icon: (active) => (
      <svg className={cn("h-5 w-5", active ? "text-ember" : "text-ink/65")} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
        <rect x="2" y="6" width="14" height="12" rx="2.5" />
        <path d="M16 10l6-3.5v11L16 14v-4z" strokeLinecap="round" strokeLinejoin="round" />
        <circle cx="9" cy="12" r="2.2" fill="currentColor" />
      </svg>
    ),
  },
  {
    href: "/tech-news",
    label: "Tech News Lab",
    shortLabel: "News",
    icon: (active) => (
      <svg className={cn("h-5 w-5", active ? "text-ember" : "text-ink/65")} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
        <path d="M4 5h13a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V5z" />
        <path d="M19 8h1a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-1M8 9h7M8 13h7M8 17h4" strokeLinecap="round" />
      </svg>
    ),
  },
  {
    href: "/exercises",
    label: "Daily Drills",
    shortLabel: "Drills",
    icon: (active) => (
      <svg className={cn("h-5 w-5", active ? "text-ember" : "text-ink/65")} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
        <path d="M13 2L3 14h8l-1 8 10-12h-8l1-8z" strokeLinecap="round" strokeLinejoin="round" />
      </svg>
    ),
  },
  {
    href: "/progress",
    label: "Progress & Compare",
    shortLabel: "Progress",
    icon: (active) => (
      <svg className={cn("h-5 w-5", active ? "text-ember" : "text-ink/65")} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
        <path d="M4 19h16M7 15V9M12 15V5M17 15v-3" strokeLinecap="round" strokeLinejoin="round" />
      </svg>
    ),
  },
];

type AppShellProps = {
  children: ReactNode;
  title?: string;
  subtitle?: string;
  eyebrow?: string;
  actions?: ReactNode;
  maxWidthClass?: string;
};

export function AppShell({
  children,
  title,
  subtitle,
  eyebrow,
  actions,
  maxWidthClass = "max-w-7xl",
}: AppShellProps) {
  const pathname = usePathname();
  const router = useRouter();
  const userEmail = useAuthStore((state) => state.userEmail);
  const logout = useAuthStore((state) => state.logout);

  const isActive = (href: string) => {
    if (href === "/") return pathname === "/";
    return pathname === href || pathname?.startsWith(`${href}/`);
  };

  const handleLogout = async () => {
    await logout();
    router.replace("/login");
  };

  return (
    <div className="min-h-screen bg-hero-gradient pb-24 lg:pb-12">
      <header className="sticky top-0 z-30 border-b border-ink/10 bg-cream/85 backdrop-blur-md">
        <div className={cn("mx-auto flex items-center justify-between gap-4 px-4 py-3 sm:px-6", maxWidthClass)}>
          <Link href="/" className="flex items-center gap-3">
            <span className="flex h-10 w-10 items-center justify-center rounded-2xl bg-ink text-white shadow-sm">
              <svg className="h-5 w-5 text-ember" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2">
                <rect x="2" y="6" width="13" height="12" rx="2.5" />
                <path d="M15 10.5l6-3.5v10l-6-3.5v-3z" strokeLinecap="round" strokeLinejoin="round" />
              </svg>
            </span>
            <div className="leading-tight">
              <p className="font-display text-base font-semibold tracking-tight text-ink">Speech-Machine</p>
              <p className="text-[11px] font-medium uppercase tracking-[0.18em] text-ink/55">
                Tech Presenter Coach
              </p>
            </div>
          </Link>

          <nav aria-label="Main navigation" className="hidden items-center gap-1 lg:flex">
            {NAV_ITEMS.map((item) => {
              const active = isActive(item.href);
              return (
                <Link
                  key={item.href}
                  href={item.href}
                  className={cn(
                    "flex items-center gap-2 rounded-full px-3.5 py-2 text-sm font-medium transition",
                    active
                      ? "bg-ink text-white shadow-sm"
                      : "text-ink/75 hover:bg-ink/5 hover:text-ink"
                  )}
                >
                  {item.label}
                </Link>
              );
            })}
          </nav>

          <div className="flex items-center gap-2">
            {pathname !== "/record" && (
              <Link href="/record" className="hidden sm:inline-flex">
                <Button size="sm" className="bg-ember text-white hover:bg-ember/90">
                  Open Studio
                </Button>
              </Link>
            )}
            {userEmail && (
              <span className="hidden max-w-[160px] truncate rounded-full bg-white/75 px-3 py-1.5 text-xs font-medium text-ink/70 xl:inline-block">
                {userEmail}
              </span>
            )}
            <Button variant="ghost" size="sm" onClick={handleLogout} title="Sign out">
              Sign out
            </Button>
          </div>
        </div>
      </header>

      <main className={cn("mx-auto px-4 pt-6 sm:px-6 lg:pt-8", maxWidthClass)}>
        {(title || actions) && (
          <div className="mb-6 flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
            <div className="space-y-1">
              {eyebrow && (
                <p className="text-xs font-semibold uppercase tracking-[0.26em] text-ember">
                  {eyebrow}
                </p>
              )}
              {title && (
                <h1 className="font-display text-2xl font-semibold tracking-tight text-ink sm:text-3xl lg:text-4xl">
                  {title}
                </h1>
              )}
              {subtitle && <p className="max-w-3xl text-sm text-ink/70 sm:text-base">{subtitle}</p>}
            </div>
            {actions && <div className="flex flex-wrap items-center gap-2.5">{actions}</div>}
          </div>
        )}
        {children}
      </main>

      <nav
        aria-label="Mobile navigation"
        className="fixed inset-x-0 bottom-0 z-40 border-t border-ink/10 bg-cream/95 px-2 pb-[calc(env(safe-area-inset-bottom)+0.35rem)] pt-1.5 backdrop-blur-md lg:hidden"
      >
        <div className="mx-auto grid max-w-lg grid-cols-6 gap-1">
          {NAV_ITEMS.map((item) => {
            const active = isActive(item.href);
            return (
              <Link
                key={item.href}
                href={item.href}
                className={cn(
                  "flex flex-col items-center justify-center gap-0.5 rounded-2xl py-1.5 text-[11px] font-medium transition",
                  active ? "bg-ink/8 font-semibold text-ink" : "text-ink/65 hover:text-ink"
                )}
              >
                {item.icon(active)}
                <span className="truncate">{item.shortLabel}</span>
              </Link>
            );
          })}
        </div>
      </nav>
    </div>
  );
}