feat: dark modesupport
This commit is contained in:
12
index.html
12
index.html
@@ -8,6 +8,18 @@
|
||||
<meta name="theme-color" content="#2C1810" />
|
||||
<link rel="apple-touch-icon" href="/icon-192.png" />
|
||||
<title>brew</title>
|
||||
<script>
|
||||
(function() {
|
||||
try {
|
||||
const theme = localStorage.getItem('brew-theme') || 'system';
|
||||
if (theme === 'dark' || (theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
} catch (e) {}
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
87
src/App.jsx
87
src/App.jsx
@@ -54,6 +54,12 @@ async function saveData(data) {
|
||||
|
||||
const uid = () => Date.now().toString(36) + Math.random().toString(36).slice(2, 7);
|
||||
|
||||
const LoadingScreen = () => (
|
||||
<div className="w-full max-w-[480px] mx-auto min-h-screen border-x border-[#E8DFD3] dark:border-[#3B2217] bg-[#FAF6F1] dark:bg-[#150B07] max-sm:border-x-0 flex items-center justify-center transition-colors duration-200">
|
||||
<div className="text-center text-[#9C8B7A] dark:text-[#C8B9A6]"><div className="text-4xl">☕</div><div className="mt-2 text-sm">Loading…</div></div>
|
||||
</div>
|
||||
);
|
||||
|
||||
// ─── Main App ───
|
||||
export default function CoffeeLogbook() {
|
||||
const { token, user, loading, logout } = useContext(AuthContext);
|
||||
@@ -120,23 +126,18 @@ export default function CoffeeLogbook() {
|
||||
setData(newData); await saveData(newData); syncData(newData);
|
||||
}, [syncData]);
|
||||
|
||||
const LoadingScreen = () => (
|
||||
<div className="w-full max-w-[480px] mx-auto min-h-screen border-x border-[#E8DFD3] bg-[#FAF6F1] max-sm:border-x-0 flex items-center justify-center">
|
||||
<div className="text-center text-[#9C8B7A]"><div className="text-4xl">☕</div><div className="mt-2 text-sm">Loading…</div></div>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (loading) return <LoadingScreen />;
|
||||
|
||||
if (!token || !user) {
|
||||
return (
|
||||
<div className="w-full max-w-[480px] mx-auto min-h-screen border-x border-[#E8DFD3] bg-[#FAF6F1] max-sm:border-x-0">
|
||||
<div className="w-full max-w-[480px] mx-auto min-h-screen border-x border-[#E8DFD3] dark:border-[#3B2217] bg-[#FAF6F1] dark:bg-[#150B07] max-sm:border-x-0 transition-colors duration-200">
|
||||
{authView === "login" ? (
|
||||
<>
|
||||
<Login onLoginSuccess={() => {}} />
|
||||
<div className="text-center mt-5 px-5">
|
||||
<p className="text-[#9C8B7A] text-sm">Don't have an account?{' '}
|
||||
<button onClick={() => setAuthView("register")} className="border-none bg-transparent text-[#8B6914] cursor-pointer underline text-sm">Register</button>
|
||||
<p className="text-[#9C8B7A] dark:text-[#C8B9A6] text-sm">Don't have an account?{' '}
|
||||
<button onClick={() => setAuthView("register")} className="border-none bg-transparent text-[#8B6914] dark:text-[#D4A325] cursor-pointer underline text-sm">Register</button>
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
@@ -144,8 +145,8 @@ export default function CoffeeLogbook() {
|
||||
<>
|
||||
<Register onRegisterSuccess={() => setAuthView("login")} />
|
||||
<div className="text-center mt-5 px-5">
|
||||
<p className="text-[#9C8B7A] text-sm">Already have an account?{' '}
|
||||
<button onClick={() => setAuthView("login")} className="border-none bg-transparent text-[#8B6914] cursor-pointer underline text-sm">Login</button>
|
||||
<p className="text-[#9C8B7A] dark:text-[#C8B9A6] text-sm">Already have an account?{' '}
|
||||
<button onClick={() => setAuthView("login")} className="border-none bg-transparent text-[#8B6914] dark:text-[#D4A325] cursor-pointer underline text-sm">Login</button>
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
@@ -180,23 +181,23 @@ export default function CoffeeLogbook() {
|
||||
const methodCounts = { pourover: 0, espresso: 0, coldbrew: 0 };
|
||||
brewLogs.forEach(l => { if (methodCounts[l.method] !== undefined) methodCounts[l.method]++; });
|
||||
|
||||
const filterPillCls = (active) => `px-3.5 py-1.5 rounded-full border text-xs font-medium whitespace-nowrap cursor-pointer transition-all ${active ? "bg-[#2C1810] text-[#FAF6F1] border-[#2C1810]" : "bg-white border-[#E8DFD3] text-[#6B5744]"}`;
|
||||
const filterPillCls = (active) => `px-3.5 py-1.5 rounded-full border text-xs font-medium whitespace-nowrap cursor-pointer transition-all ${active ? "bg-[#2C1810] text-[#FAF6F1] border-[#2C1810] dark:bg-[#FAF6F1] dark:text-[#2C1810] dark:border-[#FAF6F1]" : "bg-white border-[#E8DFD3] text-[#6B5744] dark:bg-[#22120B] dark:border-[#3B2217] dark:text-[#C8B9A6]"}`;
|
||||
|
||||
// Page title per view
|
||||
const pageTitles = { dashboard: "Brew Journal", beans: "Bean Recipes", brews: "Brew Logs", profile: "Profile" };
|
||||
const pageSubtitles = { dashboard: "Coffee Logbook", beans: "Your Collection", brews: "All Sessions", profile: "Account" };
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-[480px] mx-auto min-h-screen bg-[#FAF6F1] border-x border-[#E8DFD3] shadow-[0_0_30px_rgba(44,24,16,0.05)] max-sm:border-x-0 max-sm:shadow-none font-sans relative">
|
||||
<div className="w-full max-w-[480px] mx-auto min-h-screen bg-[#FAF6F1] dark:bg-[#150B07] border-x border-[#E8DFD3] dark:border-[#3B2217] shadow-[0_0_30px_rgba(44,24,16,0.05)] dark:shadow-none max-sm:border-x-0 max-sm:shadow-none font-sans relative text-[#2C1810] dark:text-[#FAF6F1] transition-colors duration-200">
|
||||
|
||||
{/* Header */}
|
||||
<div className="px-5 pt-6 pb-4 flex items-center justify-between sticky top-0 bg-[#FAF6F1]/95 backdrop-blur-sm z-50 border-b border-[#E8DFD3]">
|
||||
<div className="px-5 pt-6 pb-4 flex items-center justify-between sticky top-0 bg-[#FAF6F1]/95 dark:bg-[#150B07]/95 backdrop-blur-sm z-50 border-b border-[#E8DFD3] dark:border-[#3B2217] transition-colors duration-200">
|
||||
<div>
|
||||
<div className="text-[10px] text-[#9C8B7A] font-semibold tracking-[1.5px] uppercase">{pageSubtitles[view] || "Coffee Logbook"}</div>
|
||||
<h1 className="font-serif text-[21px] font-semibold tracking-tight text-[#2C1810] mt-0.5">{pageTitles[view] || "Brew Journal"}</h1>
|
||||
<div className="text-[10px] text-[#9C8B7A] dark:text-[#C8B9A6] font-semibold tracking-[1.5px] uppercase">{pageSubtitles[view] || "Coffee Logbook"}</div>
|
||||
<h1 className="font-serif text-[21px] font-semibold tracking-tight text-[#2C1810] dark:text-[#FAF6F1] mt-0.5">{pageTitles[view] || "Brew Journal"}</h1>
|
||||
</div>
|
||||
{showSyncedStatus && (
|
||||
<div className={`flex items-center gap-1 text-[11px] font-medium px-2.5 py-1 rounded-full transition-all ${isOnline ? "text-[#4A7C59] bg-[rgba(74,124,89,0.1)]" : "text-[#B44040] bg-[rgba(180,64,64,0.1)]"}`}
|
||||
<div className={`flex items-center gap-1 text-[11px] font-medium px-2.5 py-1 rounded-full transition-all ${isOnline ? "text-[#4A7C59] bg-[rgba(74,124,89,0.1)] dark:text-[#6CB281] dark:bg-[rgba(108,178,129,0.15)]" : "text-[#B44040] bg-[rgba(180,64,64,0.1)] dark:text-[#E55B5B] dark:bg-[rgba(229,91,91,0.15)]"}`}
|
||||
title={isOnline ? "Synchronized" : "Offline"}>
|
||||
<span className={`text-[10px] ${syncing ? "animate-sync-pulse" : ""}`}>●</span>
|
||||
<span>{isOnline ? (syncing ? "Syncing..." : "Synced") : "Offline"}</span>
|
||||
@@ -213,27 +214,27 @@ export default function CoffeeLogbook() {
|
||||
<div className="animate-page-enter">
|
||||
<div className="flex gap-2 mb-5">
|
||||
{[{ num: beans.length, label: "Beans" }, { num: brewLogs.length, label: "Brews" }, { num: new Set(brewLogs.map(l => l.beanId)).size, label: "Tried" }].map(s => (
|
||||
<div key={s.label} className="flex-1 bg-white border border-[#E8DFD3] rounded-2xl p-3.5 text-center">
|
||||
<div className="font-serif text-2xl font-bold text-[#2C1810]">{s.num}</div>
|
||||
<div className="text-[10px] text-[#9C8B7A] uppercase tracking-widest mt-0.5">{s.label}</div>
|
||||
<div key={s.label} className="flex-1 bg-white dark:bg-[#22120B] border border-[#E8DFD3] dark:border-[#3B2217] rounded-2xl p-3.5 text-center transition-colors duration-200">
|
||||
<div className="font-serif text-2xl font-bold text-[#2C1810] dark:text-[#FAF6F1]">{s.num}</div>
|
||||
<div className="text-[10px] text-[#9C8B7A] dark:text-[#C8B9A6] uppercase tracking-widest mt-0.5">{s.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="text-[13px] font-semibold text-[#6B5744] uppercase tracking-widest mb-3">By Method</div>
|
||||
<div className="text-[13px] font-semibold text-[#6B5744] dark:text-[#C8B9A6] uppercase tracking-widest mb-3">By Method</div>
|
||||
<div className="flex gap-2 mb-6">
|
||||
{METHODS.map(m => (
|
||||
<div key={m} className="flex-1 bg-white border border-[#E8DFD3] rounded-2xl p-3 text-center" style={{ borderTop: `3px solid ${METHOD_COLORS[m]}` }}>
|
||||
<div key={m} className="flex-1 bg-white dark:bg-[#22120B] border border-[#E8DFD3] dark:border-[#3B2217] rounded-2xl p-3 text-center transition-colors duration-200" style={{ borderTop: `3px solid ${METHOD_COLORS[m]}` }}>
|
||||
<div className="text-xl mb-1">{METHOD_ICONS[m]}</div>
|
||||
<div className="font-serif text-xl font-bold text-[#2C1810]">{methodCounts[m]}</div>
|
||||
<div className="text-[10px] text-[#9C8B7A] uppercase tracking-widest mt-0.5">{METHOD_LABELS[m]}</div>
|
||||
<div className="font-serif text-xl font-bold text-[#2C1810] dark:text-[#FAF6F1]">{methodCounts[m]}</div>
|
||||
<div className="text-[10px] text-[#9C8B7A] dark:text-[#C8B9A6] uppercase tracking-widest mt-0.5">{METHOD_LABELS[m]}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="text-[13px] font-semibold text-[#6B5744] uppercase tracking-widest mb-3">Recent Brews</div>
|
||||
<div className="text-[13px] font-semibold text-[#6B5744] dark:text-[#C8B9A6] uppercase tracking-widest mb-3">Recent Brews</div>
|
||||
{brewLogs.length === 0 ? (
|
||||
<div className="text-center py-12 text-[#9C8B7A]">
|
||||
<div className="text-4xl mb-3 opacity-50">📝</div>
|
||||
<h3 className="text-base mb-1.5 font-serif text-[#6B5744]">No brews yet</h3>
|
||||
<div className="text-center py-12 text-[#9C8B7A] dark:text-[#C8B9A6]">
|
||||
<div className="text-4xl mb-3 opacity-50 dark:opacity-80">📝</div>
|
||||
<h3 className="text-base mb-1.5 font-serif text-[#6B5744] dark:text-[#FAF6F1]">No brews yet</h3>
|
||||
<p className="text-[13px] leading-relaxed">Tap the + button to log your first brew.</p>
|
||||
</div>
|
||||
) : brewLogs.sort((a, b) => b.createdAt - a.createdAt).slice(0, 5).map(log => (
|
||||
@@ -245,32 +246,36 @@ export default function CoffeeLogbook() {
|
||||
{/* ── Bean Library ── */}
|
||||
{view === "beans" && !selectedBean && (
|
||||
<div className="animate-page-enter">
|
||||
<div className="text-[13px] font-semibold text-[#6B5744] uppercase tracking-widest mb-3">Your Beans ({beans.length})</div>
|
||||
<div className="text-[13px] font-semibold text-[#6B5744] dark:text-[#C8B9A6] uppercase tracking-widest mb-3">Your Beans ({beans.length})</div>
|
||||
{beans.length === 0 ? (
|
||||
<div className="text-center py-12 text-[#9C8B7A]">
|
||||
<div className="text-4xl mb-3 opacity-50">🫘</div>
|
||||
<h3 className="text-base mb-1.5 font-serif text-[#6B5744]">Library is empty</h3>
|
||||
<div className="text-center py-12 text-[#9C8B7A] dark:text-[#C8B9A6]">
|
||||
<div className="text-4xl mb-3 opacity-50 dark:opacity-80">🫘</div>
|
||||
<h3 className="text-base mb-1.5 font-serif text-[#6B5744] dark:text-[#FAF6F1]">Library is empty</h3>
|
||||
<p className="text-[13px] leading-relaxed">Tap + to add your first coffee bean.</p>
|
||||
</div>
|
||||
) : beans.map(bean => {
|
||||
const count = brewLogs.filter(l => l.beanId === bean.id).length;
|
||||
const roastTagCls = bean.roastType?.toLowerCase().includes("dark") ? "bg-[#E0D0BD] text-[#4A3520]" : bean.roastType?.toLowerCase().includes("medium") ? "bg-[#F0E0C8] text-[#6B4E2A]" : "bg-[#FFF3D6] text-[#8B6914]";
|
||||
const roastTagCls = bean.roastType?.toLowerCase().includes("dark")
|
||||
? "bg-[#E0D0BD] text-[#4A3520] dark:bg-[#2F1E12] dark:text-[#BCA38A]"
|
||||
: bean.roastType?.toLowerCase().includes("medium")
|
||||
? "bg-[#F0E0C8] text-[#6B4E2A] dark:bg-[#402A14] dark:text-[#D9AC7B]"
|
||||
: "bg-[#FFF3D6] text-[#8B6914] dark:bg-[#4A3912] dark:text-[#E6B640]";
|
||||
return (
|
||||
<div key={bean.id}
|
||||
className="bg-white rounded-2xl shadow-[0_1px_3px_rgba(44,24,16,0.06),0_4px_12px_rgba(44,24,16,0.04)] p-[18px] mb-3 border border-[#E8DFD3] cursor-pointer transition-all hover:shadow-[0_4px_16px_rgba(44,24,16,0.08)] active:scale-[0.99]"
|
||||
className="bg-white dark:bg-[#22120B] rounded-2xl shadow-[0_1px_3px_rgba(44,24,16,0.06),0_4px_12px_rgba(44,24,16,0.04)] dark:shadow-[0_4px_12px_rgba(0,0,0,0.2)] p-[18px] mb-3 border border-[#E8DFD3] dark:border-[#3B2217] cursor-pointer transition-all hover:shadow-[0_4px_16px_rgba(44,24,16,0.08)] dark:hover:shadow-[0_4px_16px_rgba(0,0,0,0.35)] active:scale-[0.99]"
|
||||
onClick={() => setSelectedBean(bean)}>
|
||||
<div className="flex justify-between items-start gap-3 mb-2">
|
||||
{bean.image && <img src={bean.image} alt="" className="w-14 h-14 rounded-xl object-cover flex-shrink-0" />}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="font-serif text-[17px] font-semibold text-[#2C1810]">{bean.name}</div>
|
||||
{bean.roastery && <div className="text-[13px] text-[#6B5744] mt-0.5">{bean.roastery}</div>}
|
||||
<div className="font-serif text-[17px] font-semibold text-[#2C1810] dark:text-[#FAF6F1]">{bean.name}</div>
|
||||
{bean.roastery && <div className="text-[13px] text-[#6B5744] dark:text-[#C8B9A6] mt-0.5">{bean.roastery}</div>}
|
||||
</div>
|
||||
<span className="text-[11px] px-2.5 py-1 rounded-full bg-[#F3EDE4] text-[#6B5744] font-medium flex-shrink-0">{count} brew{count !== 1 ? "s" : ""}</span>
|
||||
<span className="text-[11px] px-2.5 py-1 rounded-full bg-[#F3EDE4] dark:bg-[#2C1810] text-[#6B5744] dark:text-[#C8B9A6] font-medium flex-shrink-0">{count} brew{count !== 1 ? "s" : ""}</span>
|
||||
</div>
|
||||
{bean.tastingNotes && <div className="text-[13px] text-[#6B5744] italic mt-1.5 leading-snug">👅 {bean.tastingNotes}</div>}
|
||||
{bean.tastingNotes && <div className="text-[13px] text-[#6B5744] dark:text-[#C8B9A6] italic mt-1.5 leading-snug">👅 {bean.tastingNotes}</div>}
|
||||
<div className="flex gap-2 mt-2.5 flex-wrap">
|
||||
{bean.roastType && <span className={`text-[11px] px-2.5 py-1 rounded-full font-medium ${roastTagCls}`}>{bean.roastType}</span>}
|
||||
{bean.roastDate && <span className="text-[11px] px-2.5 py-1 rounded-full bg-[#F3EDE4] text-[#6B5744] font-medium">Roasted {bean.roastDate}</span>}
|
||||
{bean.roastDate && <span className="text-[11px] px-2.5 py-1 rounded-full bg-[#F3EDE4] dark:bg-[#2C1810] text-[#6B5744] dark:text-[#C8B9A6] font-medium">Roasted {bean.roastDate}</span>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -301,9 +306,9 @@ export default function CoffeeLogbook() {
|
||||
))}
|
||||
</div>
|
||||
{filteredLogs.length === 0 ? (
|
||||
<div className="text-center py-12 text-[#9C8B7A]">
|
||||
<div className="text-4xl mb-3 opacity-50">📋</div>
|
||||
<h3 className="text-base mb-1.5 font-serif text-[#6B5744]">No logs {brewFilter !== "all" ? `for ${METHOD_LABELS[brewFilter]}` : "yet"}</h3>
|
||||
<div className="text-center py-12 text-[#9C8B7A] dark:text-[#C8B9A6]">
|
||||
<div className="text-4xl mb-3 opacity-50 dark:opacity-80">📋</div>
|
||||
<h3 className="text-base mb-1.5 font-serif text-[#6B5744] dark:text-[#FAF6F1]">No logs {brewFilter !== "all" ? `for ${METHOD_LABELS[brewFilter]}` : "yet"}</h3>
|
||||
<p className="text-[13px] leading-relaxed">Start brewing and log your recipes here.</p>
|
||||
</div>
|
||||
) : filteredLogs.map(log => (
|
||||
|
||||
66
src/ThemeContext.jsx
Normal file
66
src/ThemeContext.jsx
Normal file
@@ -0,0 +1,66 @@
|
||||
/* eslint-disable react-refresh/only-export-components */
|
||||
import { createContext, useContext, useState, useEffect } from "react";
|
||||
|
||||
const ThemeContext = createContext();
|
||||
|
||||
export function ThemeProvider({ children }) {
|
||||
const [theme, setThemeState] = useState(() => {
|
||||
try {
|
||||
return localStorage.getItem("brew-theme") || "system";
|
||||
} catch {
|
||||
return "system";
|
||||
}
|
||||
});
|
||||
|
||||
const [resolvedTheme, setResolvedTheme] = useState("light");
|
||||
|
||||
const setTheme = (newTheme) => {
|
||||
try {
|
||||
localStorage.setItem("brew-theme", newTheme);
|
||||
} catch (e) {
|
||||
console.error("Failed to set theme in localStorage", e);
|
||||
}
|
||||
setThemeState(newTheme);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const root = window.document.documentElement;
|
||||
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
|
||||
const applyTheme = () => {
|
||||
const isDark = theme === "dark" || (theme === "system" && mediaQuery.matches);
|
||||
if (isDark) {
|
||||
root.classList.add("dark");
|
||||
setResolvedTheme("dark");
|
||||
} else {
|
||||
root.classList.remove("dark");
|
||||
setResolvedTheme("light");
|
||||
}
|
||||
};
|
||||
|
||||
applyTheme();
|
||||
|
||||
const handleChange = () => {
|
||||
if (theme === "system") {
|
||||
applyTheme();
|
||||
}
|
||||
};
|
||||
|
||||
mediaQuery.addEventListener("change", handleChange);
|
||||
return () => mediaQuery.removeEventListener("change", handleChange);
|
||||
}, [theme]);
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{ theme, setTheme, isDark: resolvedTheme === "dark" }}>
|
||||
{children}
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useTheme() {
|
||||
const context = useContext(ThemeContext);
|
||||
if (!context) {
|
||||
throw new Error("useTheme must be used within a ThemeProvider");
|
||||
}
|
||||
return context;
|
||||
}
|
||||
@@ -1,31 +1,32 @@
|
||||
import React from "react";
|
||||
import BrewCard from "./BrewCard";
|
||||
|
||||
export default function BeanDetail({ bean, logs, onBack, onEdit, onDelete }) {
|
||||
const beanLogs = logs.filter(l => l.beanId === bean.id).sort((a, b) => b.createdAt - a.createdAt);
|
||||
const roastTagCls = bean.roastType?.toLowerCase().includes("dark") ? "bg-[#E0D0BD] text-[#4A3520]"
|
||||
: bean.roastType?.toLowerCase().includes("medium") ? "bg-[#F0E0C8] text-[#6B4E2A]"
|
||||
: "bg-[#FFF3D6] text-[#8B6914]";
|
||||
const roastTagCls = bean.roastType?.toLowerCase().includes("dark")
|
||||
? "bg-[#E0D0BD] text-[#4A3520] dark:bg-[#2F1E12] dark:text-[#BCA38A]"
|
||||
: bean.roastType?.toLowerCase().includes("medium")
|
||||
? "bg-[#F0E0C8] text-[#6B4E2A] dark:bg-[#402A14] dark:text-[#D9AC7B]"
|
||||
: "bg-[#FFF3D6] text-[#8B6914] dark:bg-[#4A3912] dark:text-[#E6B640]";
|
||||
return (
|
||||
<div>
|
||||
<button className="flex items-center gap-1.5 border-none bg-transparent text-[13px] text-[#6B5744] cursor-pointer p-0 mb-4" onClick={onBack}>← Back</button>
|
||||
<div className="transition-colors duration-200">
|
||||
<button className="flex items-center gap-1.5 border-none bg-transparent text-[13px] text-[#6B5744] dark:text-[#C8B9A6] cursor-pointer p-0 mb-4" onClick={onBack}>← Back</button>
|
||||
{bean.image && <img src={bean.image} alt={bean.name} className="w-full h-44 object-cover rounded-2xl mb-4" />}
|
||||
<h2 className="font-serif text-[22px] font-semibold text-[#2C1810] mb-1">{bean.name}</h2>
|
||||
{bean.roastery && <div className="text-[#6B5744] text-sm mb-2">{bean.roastery}</div>}
|
||||
{bean.tastingNotes && <div className="text-[13px] text-[#6B5744] italic mt-1.5 leading-snug mb-2.5">👅 {bean.tastingNotes}</div>}
|
||||
<h2 className="font-serif text-[22px] font-semibold text-[#2C1810] dark:text-[#FAF6F1] mb-1">{bean.name}</h2>
|
||||
{bean.roastery && <div className="text-[#6B5744] dark:text-[#C8B9A6] text-sm mb-2">{bean.roastery}</div>}
|
||||
{bean.tastingNotes && <div className="text-[13px] text-[#6B5744] dark:text-[#C8B9A6] italic mt-1.5 leading-snug mb-2.5">👅 {bean.tastingNotes}</div>}
|
||||
<div className="flex gap-2 mt-2.5 flex-wrap">
|
||||
{bean.roastType && <span className={`text-[11px] px-2.5 py-1 rounded-full font-medium ${roastTagCls}`}>{bean.roastType}</span>}
|
||||
{bean.roastDate && <span className="text-[11px] px-2.5 py-1 rounded-full bg-[#F3EDE4] text-[#6B5744] font-medium">Roasted {bean.roastDate}</span>}
|
||||
<span className="text-[11px] px-2.5 py-1 rounded-full bg-[#F3EDE4] text-[#6B5744] font-medium">{beanLogs.length} brew{beanLogs.length !== 1 ? "s" : ""}</span>
|
||||
{bean.roastDate && <span className="text-[11px] px-2.5 py-1 rounded-full bg-[#F3EDE4] dark:bg-[#2C1810] text-[#6B5744] dark:text-[#C8B9A6] font-medium">Roasted {bean.roastDate}</span>}
|
||||
<span className="text-[11px] px-2.5 py-1 rounded-full bg-[#F3EDE4] dark:bg-[#2C1810] text-[#6B5744] dark:text-[#C8B9A6] font-medium">{beanLogs.length} brew{beanLogs.length !== 1 ? "s" : ""}</span>
|
||||
</div>
|
||||
<div className="flex gap-2 mt-4">
|
||||
<button className="flex-1 py-2.5 border border-[#E8DFD3] rounded-xl text-sm font-semibold text-[#6B5744] bg-transparent cursor-pointer hover:bg-[#F3EDE4] transition-colors" onClick={onEdit}>Edit</button>
|
||||
<button className="flex-1 py-2.5 border border-[#B44040] rounded-xl text-xs font-semibold text-[#B44040] bg-transparent cursor-pointer" onClick={onDelete}>Delete</button>
|
||||
<button className="flex-1 py-2.5 border border-[#E8DFD3] dark:border-[#3B2217] rounded-xl text-sm font-semibold text-[#6B5744] dark:text-[#C8B9A6] bg-transparent cursor-pointer hover:bg-[#F3EDE4] dark:hover:bg-[#2C1810] transition-colors duration-200" onClick={onEdit}>Edit</button>
|
||||
<button className="flex-1 py-2.5 border border-[#B44040] dark:border-[#E55B5B] rounded-xl text-xs font-semibold text-[#B44040] dark:text-[#E55B5B] bg-transparent cursor-pointer hover:bg-[rgba(180,64,64,0.05)] dark:hover:bg-[rgba(229,91,91,0.05)] transition-all duration-200" onClick={onDelete}>Delete</button>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<div className="text-[13px] font-semibold text-[#6B5744] uppercase tracking-widest mb-3">Brew History</div>
|
||||
<div className="text-[13px] font-semibold text-[#6B5744] dark:text-[#C8B9A6] uppercase tracking-widest mb-3">Brew History</div>
|
||||
{beanLogs.length === 0 ? (
|
||||
<div className="text-center py-6 text-[#9C8B7A]"><p className="text-[13px]">No brews logged with this bean yet.</p></div>
|
||||
<div className="text-center py-6 text-[#9C8B7A] dark:text-[#C8B9A6]"><p className="text-[13px]">No brews logged with this bean yet.</p></div>
|
||||
) : beanLogs.map(log => <BrewCard key={log.id} log={log} beanName={bean.name} />)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { ROAST_TYPES, inputCls, labelCls } from "../constants";
|
||||
|
||||
export default function BeanForm({ onSave, onClose, initial }) {
|
||||
@@ -38,11 +38,11 @@ export default function BeanForm({ onSave, onClose, initial }) {
|
||||
onClick={() => handleClose()}
|
||||
>
|
||||
<div
|
||||
className={`bg-[#FAF6F1] rounded-t-[24px] w-full max-w-[480px] max-h-[88vh] overflow-y-auto transition-transform duration-200 ease-in-out px-5 pb-8 ${active ? "translate-y-0" : "translate-y-full"}`}
|
||||
className={`bg-[#FAF6F1] dark:bg-[#150B07] rounded-t-[24px] w-full max-w-[480px] max-h-[88vh] overflow-y-auto transition-transform duration-200 ease-in-out px-5 pb-8 ${active ? "translate-y-0" : "translate-y-full"}`}
|
||||
onClick={e => e.stopPropagation()}
|
||||
>
|
||||
<div className="w-9 h-1 bg-[#E8DFD3] rounded mx-auto my-3" />
|
||||
<div className="font-serif text-xl font-semibold text-[#2C1810] mb-5">{initial ? "Edit Bean" : "Add Bean"}</div>
|
||||
<div className="w-9 h-1 bg-[#E8DFD3] dark:bg-[#3B2217] rounded mx-auto my-3" />
|
||||
<div className="font-serif text-xl font-semibold text-[#2C1810] dark:text-[#FAF6F1] mb-5">{initial ? "Edit Bean" : "Add Bean"}</div>
|
||||
<div className="mb-4"><label className={labelCls}>Bean Name *</label>
|
||||
<input className={inputCls} placeholder="e.g. Ethiopia Yirgacheffe" value={form.name} onChange={e => set("name", e.target.value)} /></div>
|
||||
<div className="mb-4"><label className={labelCls}>Roastery</label>
|
||||
@@ -58,20 +58,20 @@ export default function BeanForm({ onSave, onClose, initial }) {
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<label className={labelCls}>Bean Photo</label>
|
||||
<div className={`relative border-2 rounded-lg text-center cursor-pointer transition-all overflow-hidden ${form.image ? "border-[#E8DFD3] p-0" : "border-dashed border-[#E8DFD3] p-5 hover:border-[#8B6914]"}`}>
|
||||
<div className={`relative border-2 rounded-lg text-center cursor-pointer transition-all overflow-hidden ${form.image ? "border-[#E8DFD3] dark:border-[#3B2217] p-0" : "border-dashed border-[#E8DFD3] dark:border-[#3B2217] p-5 hover:border-[#8B6914] dark:hover:border-[#D4A325]"}`}>
|
||||
{form.image ? (
|
||||
<><img src={form.image} alt="Bean" className="w-full h-40 object-cover rounded-lg block" />
|
||||
<button className="absolute top-2 right-2 w-7 h-7 rounded-full bg-[rgba(44,24,16,0.7)] text-white border-none text-sm cursor-pointer flex items-center justify-center"
|
||||
<button className="absolute top-2 right-2 w-7 h-7 rounded-full bg-[rgba(44,24,16,0.7)] dark:bg-[rgba(250,246,241,0.8)] text-white dark:text-[#2C1810] border-none text-sm cursor-pointer flex items-center justify-center font-bold"
|
||||
onClick={(e) => { e.stopPropagation(); set("image", ""); }} type="button">×</button></>
|
||||
) : (<><div className="text-3xl mb-1.5 opacity-40">📷</div><div className="text-xs text-[#9C8B7A]">Tap to upload a photo</div></>)}
|
||||
) : (<><div className="text-3xl mb-1.5 opacity-40 dark:opacity-75">📷</div><div className="text-xs text-[#9C8B7A] dark:text-[#C8B9A6]">Tap to upload a photo</div></>)}
|
||||
<input type="file" accept="image/*" onChange={handleImage} className="absolute inset-0 opacity-0 cursor-pointer" />
|
||||
</div>
|
||||
<div className="text-[11px] text-[#9C8B7A] mt-1">Max 2 MB · JPG, PNG, or WebP</div>
|
||||
<div className="text-[11px] text-[#9C8B7A] dark:text-[#C8B9A6] mt-1">Max 2 MB · JPG, PNG, or WebP</div>
|
||||
</div>
|
||||
<div className="mb-4"><label className={labelCls}>Tasting Notes</label>
|
||||
<textarea className={`${inputCls} resize-y min-h-[80px]`} placeholder="e.g. Citrus, dark chocolate, floral…" value={form.tastingNotes || ""} onChange={e => set("tastingNotes", e.target.value)} />
|
||||
<div className="text-[11px] text-[#9C8B7A] mt-1">Flavour profile from the bag or your own impressions</div></div>
|
||||
<button className="w-full py-3.5 border-none rounded-xl bg-[#2C1810] text-[#FAF6F1] text-sm font-semibold cursor-pointer mt-2"
|
||||
<div className="text-[11px] text-[#9C8B7A] dark:text-[#C8B9A6] mt-1">Flavour profile from the bag or your own impressions</div></div>
|
||||
<button className="w-full py-3.5 border-none rounded-xl bg-[#2C1810] dark:bg-[#FAF6F1] text-[#FAF6F1] dark:text-[#2C1810] text-sm font-semibold cursor-pointer mt-2"
|
||||
style={{ opacity: canSave ? 1 : 0.4, cursor: canSave ? "pointer" : "not-allowed" }} disabled={!canSave}
|
||||
onClick={() => { if (canSave) handleClose(() => onSave(form)); }}>
|
||||
{initial ? "Save Changes" : "Add to Library"}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import { Home, Coffee, Plus, Bookmark, User } from "lucide-react";
|
||||
|
||||
export default function BottomNav({ view, setView, setSelectedBean, onCreatePress }) {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import { METHOD_COLORS, METHOD_ICONS, METHOD_LABELS } from "../constants";
|
||||
|
||||
export default function BrewCard({ log, beanName }) {
|
||||
@@ -10,11 +9,11 @@ export default function BrewCard({ log, beanName }) {
|
||||
return (
|
||||
<div className="relative mb-3">
|
||||
<div className="brew-method-bar" style={{ background: color }} />
|
||||
<div className="bg-white rounded-xl shadow-[0_1px_3px_rgba(44,24,16,0.06),0_4px_12px_rgba(44,24,16,0.04)] p-[18px] pl-6 border border-[#E8DFD3]">
|
||||
<div className="bg-white dark:bg-[#22120B] rounded-xl shadow-[0_1px_3px_rgba(44,24,16,0.06),0_4px_12px_rgba(44,24,16,0.04)] dark:shadow-[0_4px_12px_rgba(0,0,0,0.2)] p-[18px] pl-6 border border-[#E8DFD3] dark:border-[#3B2217] transition-colors duration-200">
|
||||
<div className="flex justify-between items-start">
|
||||
<div>
|
||||
<div className="text-[11px] text-[#9C8B7A] tracking-wide">{new Date(log.createdAt).toLocaleDateString("en-IN", { day: "numeric", month: "short", year: "numeric" })}</div>
|
||||
{beanName && <div className="text-sm font-semibold text-[#2C1810] mt-0.5">{beanName}</div>}
|
||||
<div className="text-[11px] text-[#9C8B7A] dark:text-[#C8B9A6] tracking-wide">{new Date(log.createdAt).toLocaleDateString("en-IN", { day: "numeric", month: "short", year: "numeric" })}</div>
|
||||
{beanName && <div className="text-sm font-semibold text-[#2C1810] dark:text-[#FAF6F1] mt-0.5">{beanName}</div>}
|
||||
</div>
|
||||
<div className="text-[11px] font-semibold uppercase tracking-widest" style={{ color }}>{METHOD_ICONS[log.method]} {METHOD_LABELS[log.method]}</div>
|
||||
</div>
|
||||
@@ -22,14 +21,14 @@ export default function BrewCard({ log, beanName }) {
|
||||
<div className="flex flex-wrap gap-3 mt-2.5">
|
||||
{allFields.map(([k, v]) => (
|
||||
<div key={k} className="text-xs">
|
||||
<div className="text-[#9C8B7A] text-[10px] uppercase tracking-wide">{fieldLabels[k] || k}</div>
|
||||
<div className="font-semibold mt-0.5">{v}</div>
|
||||
<div className="text-[#9C8B7A] dark:text-[#C8B9A6] text-[10px] uppercase tracking-wide">{fieldLabels[k] || k}</div>
|
||||
<div className="font-semibold mt-0.5 text-[#2C1810] dark:text-[#FAF6F1]">{v}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{log.recipeDetails && <div className="text-[13px] text-[#6B5744] mt-2.5 italic leading-relaxed">📝 {log.recipeDetails}</div>}
|
||||
{log.tasteNotes && <div className="text-[13px] text-[#6B5744] mt-1.5 italic leading-relaxed">👅 {log.tasteNotes}</div>}
|
||||
{log.recipeDetails && <div className="text-[13px] text-[#6B5744] dark:text-[#C8B9A6] mt-2.5 italic leading-relaxed">📝 {log.recipeDetails}</div>}
|
||||
{log.tasteNotes && <div className="text-[13px] text-[#6B5744] dark:text-[#C8B9A6] mt-1.5 italic leading-relaxed">👅 {log.tasteNotes}</div>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { METHODS, METHOD_LABELS, METHOD_ICONS, METHOD_COLORS, inputCls, labelCls } from "../constants";
|
||||
|
||||
export default function BrewForm({ beans, onSave, onClose }) {
|
||||
@@ -30,17 +30,17 @@ export default function BrewForm({ beans, onSave, onClose }) {
|
||||
onClick={() => handleClose()}
|
||||
>
|
||||
<div
|
||||
className={`bg-[#FAF6F1] rounded-t-[24px] w-full max-w-[480px] max-h-[88vh] overflow-y-auto transition-transform duration-200 ease-in-out px-5 pb-8 ${active ? "translate-y-0" : "translate-y-full"}`}
|
||||
className={`bg-[#FAF6F1] dark:bg-[#150B07] rounded-t-[24px] w-full max-w-[480px] max-h-[88vh] overflow-y-auto transition-transform duration-200 ease-in-out px-5 pb-8 ${active ? "translate-y-0" : "translate-y-full"}`}
|
||||
onClick={e => e.stopPropagation()}
|
||||
>
|
||||
<div className="w-9 h-1 bg-[#E8DFD3] rounded mx-auto my-3" />
|
||||
<div className="font-serif text-xl font-semibold text-[#2C1810] mb-5">Log a Brew</div>
|
||||
<div className="text-center py-12 text-[#9C8B7A]">
|
||||
<div className="text-4xl mb-3 opacity-50">🫘</div>
|
||||
<h3 className="text-base mb-1.5 text-[#6B5744]">No beans yet</h3>
|
||||
<div className="w-9 h-1 bg-[#E8DFD3] dark:bg-[#3B2217] rounded mx-auto my-3" />
|
||||
<div className="font-serif text-xl font-semibold text-[#2C1810] dark:text-[#FAF6F1] mb-5">Log a Brew</div>
|
||||
<div className="text-center py-12 text-[#9C8B7A] dark:text-[#C8B9A6]">
|
||||
<div className="text-4xl mb-3 opacity-50 dark:opacity-80">🫘</div>
|
||||
<h3 className="text-base mb-1.5 text-[#6B5744] dark:text-[#FAF6F1]">No beans yet</h3>
|
||||
<p className="text-[13px] leading-relaxed">Add a bean to your library first.</p>
|
||||
</div>
|
||||
<button className="w-full py-3.5 border border-[#E8DFD3] rounded-xl text-sm font-semibold text-[#6B5744] bg-transparent cursor-pointer" onClick={() => handleClose()}>Close</button>
|
||||
<button className="w-full py-3.5 border border-[#E8DFD3] dark:border-[#3B2217] rounded-xl text-sm font-semibold text-[#6B5744] dark:text-[#C8B9A6] bg-transparent cursor-pointer hover:bg-[#F3EDE4] dark:hover:bg-[#2C1810]" onClick={() => handleClose()}>Close</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -75,15 +75,15 @@ export default function BrewForm({ beans, onSave, onClose }) {
|
||||
onClick={() => handleClose()}
|
||||
>
|
||||
<div
|
||||
className={`bg-[#FAF6F1] rounded-t-[24px] w-full max-w-[480px] max-h-[88vh] overflow-y-auto transition-transform duration-200 ease-in-out px-5 pb-8 ${active ? "translate-y-0" : "translate-y-full"}`}
|
||||
className={`bg-[#FAF6F1] dark:bg-[#150B07] rounded-t-[24px] w-full max-w-[480px] max-h-[88vh] overflow-y-auto transition-transform duration-200 ease-in-out px-5 pb-8 ${active ? "translate-y-0" : "translate-y-full"}`}
|
||||
onClick={e => e.stopPropagation()}
|
||||
>
|
||||
<div className="w-9 h-1 bg-[#E8DFD3] rounded mx-auto my-3" />
|
||||
<div className="font-serif text-xl font-semibold text-[#2C1810] mb-5">Log a Brew</div>
|
||||
<div className="w-9 h-1 bg-[#E8DFD3] dark:bg-[#3B2217] rounded mx-auto my-3" />
|
||||
<div className="font-serif text-xl font-semibold text-[#2C1810] dark:text-[#FAF6F1] mb-5">Log a Brew</div>
|
||||
<div className="flex gap-1.5 mb-5">
|
||||
{METHODS.map(m => (
|
||||
<button key={m}
|
||||
className={`flex-1 py-3 px-2 border-2 bg-white rounded-xl cursor-pointer text-center text-xs font-semibold transition-all ${method === m ? "border-current" : "border-[#E8DFD3] text-[#9C8B7A]"}`}
|
||||
className={`flex-1 py-3 px-2 border-2 bg-white dark:bg-[#22120B] rounded-xl cursor-pointer text-center text-xs font-semibold transition-all ${method === m ? "border-current" : "border-[#E8DFD3] dark:border-[#3B2217] text-[#9C8B7A] dark:text-[#C8B9A6]"}`}
|
||||
style={{ color: method === m ? METHOD_COLORS[m] : undefined }}
|
||||
onClick={() => setMethod(m)}>
|
||||
<div className="text-xl mb-1">{METHOD_ICONS[m]}</div>{METHOD_LABELS[m]}
|
||||
@@ -92,7 +92,7 @@ export default function BrewForm({ beans, onSave, onClose }) {
|
||||
</div>
|
||||
<div className="mb-4"><label className={labelCls}>Bean *</label>
|
||||
<select className={inputCls} value={form.beanId} onChange={e => set("beanId", e.target.value)}>
|
||||
{beans.map(b => <option key={b.id} value={b.id}>{b.name}{b.roastery ? ` — ${b.roastery}` : ""}</option>)}
|
||||
{beans.map(b => <option key={b.id} value={b.id} className="dark:bg-[#150B07]">{b.name}{b.roastery ? ` — ${b.roastery}` : ""}</option>)}
|
||||
</select></div>
|
||||
<div className="flex flex-wrap gap-2.5 mb-4">
|
||||
{fields[method].map(f => (
|
||||
@@ -100,7 +100,7 @@ export default function BrewForm({ beans, onSave, onClose }) {
|
||||
<label className={labelCls}>{f.label}</label>
|
||||
<input className={inputCls} type={f.type || "text"} placeholder={f.placeholder}
|
||||
value={form[f.key] || ""} onChange={e => set(f.key, e.target.value)} />
|
||||
{f.hint && <div className="text-[11px] text-[#9C8B7A] mt-1">{f.hint}</div>}
|
||||
{f.hint && <div className="text-[11px] text-[#9C8B7A] dark:text-[#C8B9A6] mt-1">{f.hint}</div>}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -111,7 +111,7 @@ export default function BrewForm({ beans, onSave, onClose }) {
|
||||
<div className="mb-4"><label className={labelCls}>Taste Notes</label>
|
||||
<input className={inputCls} placeholder="e.g. citrus, chocolate, floral"
|
||||
value={form.tasteNotes || ""} onChange={e => set("tasteNotes", e.target.value)} /></div>
|
||||
<button className="w-full py-3.5 border-none rounded-xl bg-[#2C1810] text-[#FAF6F1] text-sm font-semibold cursor-pointer hover:opacity-90 mt-2"
|
||||
<button className="w-full py-3.5 border-none rounded-xl bg-[#2C1810] dark:bg-[#FAF6F1] text-[#FAF6F1] dark:text-[#2C1810] text-sm font-semibold cursor-pointer hover:opacity-90 mt-2"
|
||||
onClick={() => handleClose(() => onSave({ ...form, method }))}>Save Brew Log</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
export default function CreateModal({ onClose, onAddBean, onAddBrew }) {
|
||||
const [active, setActive] = useState(false);
|
||||
@@ -25,28 +25,28 @@ export default function CreateModal({ onClose, onAddBean, onAddBrew }) {
|
||||
onClick={() => handleClose()}
|
||||
>
|
||||
<div
|
||||
className={`bg-[#FAF6F1] rounded-t-[28px] w-full max-w-[480px] px-5 pb-10 transition-transform duration-200 ease-in-out ${active ? "translate-y-0" : "translate-y-full"}`}
|
||||
className={`bg-[#FAF6F1] dark:bg-[#150B07] rounded-t-[28px] w-full max-w-[480px] px-5 pb-10 transition-transform duration-200 ease-in-out ${active ? "translate-y-0" : "translate-y-full"}`}
|
||||
onClick={e => e.stopPropagation()}
|
||||
>
|
||||
<div className="w-9 h-1 bg-[#E8DFD3] rounded mx-auto mt-3 mb-6" />
|
||||
<div className="font-serif text-xl font-semibold text-[#2C1810] mb-6">What would you like to add?</div>
|
||||
<div className="w-9 h-1 bg-[#E8DFD3] dark:bg-[#3B2217] rounded mx-auto mt-3 mb-6" />
|
||||
<div className="font-serif text-xl font-semibold text-[#2C1810] dark:text-[#FAF6F1] mb-6">What would you like to add?</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
<button
|
||||
className="flex items-center gap-4 px-5 py-4 bg-white border border-[#E8DFD3] rounded-2xl text-left cursor-pointer hover:shadow-[0_4px_16px_rgba(44,24,16,0.08)] transition-all active:scale-[0.98]"
|
||||
className="flex items-center gap-4 px-5 py-4 bg-white dark:bg-[#22120B] border border-[#E8DFD3] dark:border-[#3B2217] rounded-2xl text-left cursor-pointer hover:shadow-[0_4px_16px_rgba(44,24,16,0.08)] dark:hover:shadow-[0_4px_16px_rgba(0,0,0,0.3)] transition-all active:scale-[0.98]"
|
||||
onClick={() => handleClose(onAddBrew)}>
|
||||
<div className="w-12 h-12 rounded-xl bg-[#FAF6F1] flex items-center justify-center text-2xl flex-shrink-0">☕</div>
|
||||
<div className="w-12 h-12 rounded-xl bg-[#FAF6F1] dark:bg-[#2C1810] flex items-center justify-center text-2xl flex-shrink-0">☕</div>
|
||||
<div>
|
||||
<div className="font-semibold text-[#2C1810] text-sm">Log a Brew</div>
|
||||
<div className="text-xs text-[#9C8B7A] mt-0.5">Record a brewing session with recipe details</div>
|
||||
<div className="font-semibold text-[#2C1810] dark:text-[#FAF6F1] text-sm">Log a Brew</div>
|
||||
<div className="text-xs text-[#9C8B7A] dark:text-[#C8B9A6] mt-0.5">Record a brewing session with recipe details</div>
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
className="flex items-center gap-4 px-5 py-4 bg-white border border-[#E8DFD3] rounded-2xl text-left cursor-pointer hover:shadow-[0_4px_16px_rgba(44,24,16,0.08)] transition-all active:scale-[0.98]"
|
||||
className="flex items-center gap-4 px-5 py-4 bg-white dark:bg-[#22120B] border border-[#E8DFD3] dark:border-[#3B2217] rounded-2xl text-left cursor-pointer hover:shadow-[0_4px_16px_rgba(44,24,16,0.08)] dark:hover:shadow-[0_4px_16px_rgba(0,0,0,0.3)] transition-all active:scale-[0.98]"
|
||||
onClick={() => handleClose(onAddBean)}>
|
||||
<div className="w-12 h-12 rounded-xl bg-[#FAF6F1] flex items-center justify-center text-2xl flex-shrink-0">🫘</div>
|
||||
<div className="w-12 h-12 rounded-xl bg-[#FAF6F1] dark:bg-[#2C1810] flex items-center justify-center text-2xl flex-shrink-0">🫘</div>
|
||||
<div>
|
||||
<div className="font-semibold text-[#2C1810] text-sm">Add Bean</div>
|
||||
<div className="text-xs text-[#9C8B7A] mt-0.5">Add a new coffee bean to your library</div>
|
||||
<div className="font-semibold text-[#2C1810] dark:text-[#FAF6F1] text-sm">Add Bean</div>
|
||||
<div className="text-xs text-[#9C8B7A] dark:text-[#C8B9A6] mt-0.5">Add a new coffee bean to your library</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
export default function IosPromptModal() {
|
||||
const [show, setShow] = useState(false);
|
||||
@@ -39,16 +39,16 @@ export default function IosPromptModal() {
|
||||
onClick={handleClose}
|
||||
>
|
||||
<div
|
||||
className={`bg-[#FAF6F1] rounded-t-[28px] w-full max-w-[480px] px-6 pb-8 pt-4 relative shadow-[0_-8px_30px_rgba(44,24,16,0.15)] transition-transform duration-200 ease-in-out ${active ? "translate-y-0" : "translate-y-full"}`}
|
||||
className={`bg-[#FAF6F1] dark:bg-[#150B07] rounded-t-[28px] w-full max-w-[480px] px-6 pb-8 pt-4 relative shadow-[0_-8px_30px_rgba(44,24,16,0.15)] dark:shadow-none transition-transform duration-200 ease-in-out ${active ? "translate-y-0" : "translate-y-full"}`}
|
||||
onClick={e => e.stopPropagation()}
|
||||
>
|
||||
{/* Handle bar */}
|
||||
<div className="w-9 h-1 bg-[#E8DFD3] rounded mx-auto mb-6" />
|
||||
<div className="w-9 h-1 bg-[#E8DFD3] dark:bg-[#3B2217] rounded mx-auto mb-6" />
|
||||
|
||||
{/* Close Button */}
|
||||
<button
|
||||
onClick={handleClose}
|
||||
className="absolute right-5 top-5 w-8 h-8 flex items-center justify-center rounded-full bg-white border border-[#E8DFD3] text-[#9C8B7A] hover:text-[#2C1810] transition-colors cursor-pointer text-lg font-semibold"
|
||||
className="absolute right-5 top-5 w-8 h-8 flex items-center justify-center rounded-full bg-white dark:bg-[#22120B] border border-[#E8DFD3] dark:border-[#3B2217] text-[#9C8B7A] dark:text-[#C8B9A6] hover:text-[#2C1810] dark:hover:text-[#FAF6F1] transition-colors cursor-pointer text-lg font-semibold"
|
||||
aria-label="Close"
|
||||
>
|
||||
×
|
||||
@@ -57,28 +57,28 @@ export default function IosPromptModal() {
|
||||
{/* Content */}
|
||||
<div className="flex flex-col items-center text-center">
|
||||
{/* Brew Logo */}
|
||||
<div className="w-16 h-16 bg-white border border-[#E8DFD3] rounded-2xl flex items-center justify-center p-3 shadow-sm mb-3">
|
||||
<div className="w-16 h-16 bg-white dark:bg-[#22120B] border border-[#E8DFD3] dark:border-[#3B2217] rounded-2xl flex items-center justify-center p-3 shadow-sm mb-3">
|
||||
<img src="/favicon.svg" alt="Brew Logo" className="w-full h-full object-contain" />
|
||||
</div>
|
||||
|
||||
{/* Brew Text */}
|
||||
<div className="font-serif text-2xl font-bold text-[#2C1810] mb-3">Brew</div>
|
||||
<div className="font-serif text-2xl font-bold text-[#2C1810] dark:text-[#FAF6F1] mb-3">Brew</div>
|
||||
|
||||
{/* Description */}
|
||||
<p className="text-sm text-[#6B5744] leading-relaxed mb-6 px-4">
|
||||
<p className="text-sm text-[#6B5744] dark:text-[#C8B9A6] leading-relaxed mb-6 px-4">
|
||||
Open this page on safari and then install this app to your homescreen for a better experience.
|
||||
</p>
|
||||
|
||||
{/* Instruction */}
|
||||
<div className="w-full bg-white border border-[#E8DFD3] rounded-2xl p-4 flex items-center justify-center gap-3">
|
||||
<span className="text-sm text-[#2C1810] font-medium flex items-center justify-center flex-wrap">
|
||||
<div className="w-full bg-white dark:bg-[#22120B] border border-[#E8DFD3] dark:border-[#3B2217] rounded-2xl p-4 flex items-center justify-center gap-3">
|
||||
<span className="text-sm text-[#2C1810] dark:text-[#FAF6F1] font-medium flex items-center justify-center flex-wrap">
|
||||
Tap
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#8B6914" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" className="inline-block mx-1.5 align-middle">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#D4A325" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" className="inline-block mx-1.5 align-middle">
|
||||
<path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8" />
|
||||
<polyline points="16 6 12 2 8 6" />
|
||||
<line x1="12" y1="2" x2="12" y2="15" />
|
||||
</svg>
|
||||
then <span className="font-bold text-[#8B6914] ml-1">"Add to Home Screen"</span>
|
||||
then <span className="font-bold text-[#8B6914] dark:text-[#D4A325] ml-1">"Add to Home Screen"</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { RefreshCw } from "lucide-react";
|
||||
|
||||
export default function UpdatePrompt() {
|
||||
|
||||
@@ -4,5 +4,5 @@ export const METHOD_LABELS = { pourover: "Pour Over", espresso: "Espresso", cold
|
||||
export const METHOD_ICONS = { pourover: "☕", espresso: "⚡", coldbrew: "❄️" };
|
||||
export const METHOD_COLORS = { pourover: "#8B6914", espresso: "#5C3317", coldbrew: "#2F4F6F" };
|
||||
|
||||
export const inputCls = "w-full px-3.5 py-3 border border-[#E8DFD3] rounded-lg bg-white text-sm text-[#2C1810] transition-colors outline-none focus:border-[#8B6914]";
|
||||
export const labelCls = "block text-[10px] font-semibold uppercase tracking-wider text-[#6B5744] mb-1.5";
|
||||
export const inputCls = "w-full px-3.5 py-3 border border-[#E8DFD3] dark:border-[#3B2217] rounded-lg bg-white dark:bg-[#150B07] text-sm text-[#2C1810] dark:text-[#FAF6F1] transition-colors outline-none focus:border-[#8B6914] dark:focus:border-[#D4A325]";
|
||||
export const labelCls = "block text-[10px] font-semibold uppercase tracking-wider text-[#6B5744] dark:text-[#C8B9A6] mb-1.5";
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@custom-variant dark (&:where(.dark, .dark *));
|
||||
|
||||
@theme {
|
||||
--color-brew-bg: #FAF6F1;
|
||||
--color-brew-bg2: #F3EDE4;
|
||||
@@ -40,11 +42,21 @@
|
||||
background-color: #F3EDE4;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
transition: background-color 0.2s ease, color 0.2s ease;
|
||||
}
|
||||
|
||||
html.dark {
|
||||
background-color: #0E0704;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background-color: #F3EDE4;
|
||||
transition: background-color 0.2s ease, color 0.2s ease;
|
||||
}
|
||||
|
||||
html.dark body {
|
||||
background-color: #0E0704;
|
||||
}
|
||||
|
||||
#root {
|
||||
|
||||
@@ -3,11 +3,14 @@ import { createRoot } from 'react-dom/client'
|
||||
import './index.css'
|
||||
import App from './App.jsx'
|
||||
import { AuthProvider } from './AuthContext.jsx'
|
||||
import { ThemeProvider } from './ThemeContext.jsx'
|
||||
|
||||
createRoot(document.getElementById('root')).render(
|
||||
<StrictMode>
|
||||
<AuthProvider>
|
||||
<App />
|
||||
<ThemeProvider>
|
||||
<App />
|
||||
</ThemeProvider>
|
||||
</AuthProvider>
|
||||
</StrictMode>,
|
||||
)
|
||||
|
||||
@@ -22,15 +22,15 @@ export default function Login({ onLoginSuccess }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="px-5 pt-12 pb-8 max-w-[480px] mx-auto">
|
||||
<div className="px-5 pt-12 pb-8 max-w-[480px] mx-auto transition-colors duration-200">
|
||||
<div className="mb-8 text-center">
|
||||
<div className="text-5xl mb-3">☕</div>
|
||||
<h1 className="font-serif text-3xl font-semibold text-[#2C1810] mb-1">Brew Journal</h1>
|
||||
<p className="text-sm text-[#9C8B7A]">Sign in to your coffee logbook</p>
|
||||
<h1 className="font-serif text-3xl font-semibold text-[#2C1810] dark:text-[#FAF6F1] mb-1">Brew Journal</h1>
|
||||
<p className="text-sm text-[#9C8B7A] dark:text-[#C8B9A6]">Sign in to your coffee logbook</p>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="bg-[rgba(180,64,64,0.08)] border border-[rgba(180,64,64,0.2)] text-[#B44040] text-sm px-4 py-3 rounded-lg mb-4">
|
||||
<div className="bg-[rgba(180,64,64,0.08)] dark:bg-[rgba(229,91,91,0.08)] border border-[rgba(180,64,64,0.2)] dark:border-[rgba(229,91,91,0.2)] text-[#B44040] dark:text-[#E55B5B] text-sm px-4 py-3 rounded-lg mb-4">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
@@ -61,7 +61,7 @@ export default function Login({ onLoginSuccess }) {
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full py-3.5 border-none rounded-lg bg-[#2C1810] text-[#FAF6F1] text-sm font-semibold tracking-wide cursor-pointer transition-opacity mt-1"
|
||||
className="w-full py-3.5 border-none rounded-lg bg-[#2C1810] dark:bg-[#FAF6F1] text-[#FAF6F1] dark:text-[#2C1810] text-sm font-semibold tracking-wide cursor-pointer transition-colors mt-1"
|
||||
style={{ opacity: loading ? 0.6 : 1, cursor: loading ? 'not-allowed' : 'pointer' }}
|
||||
>
|
||||
{loading ? 'Signing in…' : 'Sign In'}
|
||||
|
||||
@@ -1,19 +1,29 @@
|
||||
import React, { useContext } from "react";
|
||||
/* global __APP_VERSION__ */
|
||||
import { useContext } from "react";
|
||||
import { AuthContext } from "../AuthContext";
|
||||
import { useTheme } from "../ThemeContext";
|
||||
|
||||
export default function ProfilePage({ user, isOnline, syncing, showSyncedStatus }) {
|
||||
const { logout } = useContext(AuthContext);
|
||||
const { theme, setTheme } = useTheme();
|
||||
|
||||
const themeOptions = [
|
||||
{ value: "light", label: "Light" },
|
||||
{ value: "dark", label: "Dark" },
|
||||
{ value: "system", label: "System" }
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="transition-colors duration-200">
|
||||
{/* Avatar & Name */}
|
||||
<div className="flex flex-col items-center pt-6 pb-8">
|
||||
<div className="w-20 h-20 rounded-full bg-[#2C1810] flex items-center justify-center text-3xl text-[#FAF6F1] font-serif font-bold mb-3">
|
||||
<div className="w-20 h-20 rounded-full bg-[#2C1810] dark:bg-[#D4A325] flex items-center justify-center text-3xl text-[#FAF6F1] dark:text-[#2C1810] font-serif font-bold mb-3 shadow-sm">
|
||||
{user?.username?.[0]?.toUpperCase() || "☕"}
|
||||
</div>
|
||||
<div className="font-serif text-xl font-semibold text-[#2C1810]">{user?.username}</div>
|
||||
<div className="text-sm text-[#9C8B7A] mt-0.5">{user?.email}</div>
|
||||
<div className="font-serif text-xl font-semibold text-[#2C1810] dark:text-[#FAF6F1]">{user?.username}</div>
|
||||
<div className="text-sm text-[#9C8B7A] dark:text-[#C8B9A6] mt-0.5">{user?.email}</div>
|
||||
{showSyncedStatus && (
|
||||
<div className={`flex items-center gap-1.5 text-[11px] font-medium px-3 py-1.5 rounded-full mt-3 transition-all ${isOnline ? "text-[#4A7C59] bg-[rgba(74,124,89,0.1)]" : "text-[#B44040] bg-[rgba(180,64,64,0.1)]"}`}>
|
||||
<div className={`flex items-center gap-1.5 text-[11px] font-medium px-3 py-1.5 rounded-full mt-3 transition-all ${isOnline ? "text-[#4A7C59] bg-[rgba(74,124,89,0.1)] dark:text-[#6CB281] dark:bg-[rgba(108,178,129,0.15)]" : "text-[#B44040] bg-[rgba(180,64,64,0.1)] dark:text-[#E55B5B] dark:bg-[rgba(229,91,91,0.15)]"}`}>
|
||||
<span className={`text-[10px] ${syncing ? "animate-sync-pulse" : ""}`}>●</span>
|
||||
<span>{isOnline ? (syncing ? "Syncing…" : "All data synced") : "Offline — saved locally"}</span>
|
||||
</div>
|
||||
@@ -22,41 +32,76 @@ export default function ProfilePage({ user, isOnline, syncing, showSyncedStatus
|
||||
|
||||
{/* Account section */}
|
||||
<div className="mb-6">
|
||||
<div className="text-[11px] font-semibold uppercase tracking-widest text-[#9C8B7A] mb-2 px-1">Account</div>
|
||||
<div className="bg-white rounded-2xl border border-[#E8DFD3] overflow-hidden">
|
||||
<div className="flex items-center gap-3 px-4 py-3.5 border-b border-[#F3EDE4]">
|
||||
<div className="text-[11px] font-semibold uppercase tracking-widest text-[#9C8B7A] dark:text-[#C8B9A6] mb-2 px-1">Account</div>
|
||||
<div className="bg-white dark:bg-[#22120B] rounded-2xl border border-[#E8DFD3] dark:border-[#3B2217] overflow-hidden shadow-sm transition-colors duration-200">
|
||||
<div className="flex items-center gap-3 px-4 py-3.5 border-b border-[#F3EDE4] dark:border-[#3B2217]">
|
||||
<span className="text-lg">👤</span>
|
||||
<div>
|
||||
<div className="text-xs text-[#9C8B7A]">Username</div>
|
||||
<div className="text-sm font-medium text-[#2C1810]">{user?.username}</div>
|
||||
<div className="text-xs text-[#9C8B7A] dark:text-[#C8B9A6]">Username</div>
|
||||
<div className="text-sm font-medium text-[#2C1810] dark:text-[#FAF6F1]">{user?.username}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 px-4 py-3.5">
|
||||
<span className="text-lg">✉️</span>
|
||||
<div>
|
||||
<div className="text-xs text-[#9C8B7A]">Email</div>
|
||||
<div className="text-sm font-medium text-[#2C1810]">{user?.email}</div>
|
||||
<div className="text-xs text-[#9C8B7A] dark:text-[#C8B9A6]">Email</div>
|
||||
<div className="text-sm font-medium text-[#2C1810] dark:text-[#FAF6F1]">{user?.email}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Appearance Settings section */}
|
||||
<div className="mb-6">
|
||||
<div className="text-[11px] font-semibold uppercase tracking-widest text-[#9C8B7A] dark:text-[#C8B9A6] mb-2 px-1">Appearance</div>
|
||||
<div className="bg-white dark:bg-[#22120B] rounded-2xl border border-[#E8DFD3] dark:border-[#3B2217] p-4 flex flex-col gap-3 shadow-sm transition-colors duration-200">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-lg">🌓</span>
|
||||
<div>
|
||||
<div className="text-sm font-medium text-[#2C1810] dark:text-[#FAF6F1]">Theme</div>
|
||||
<div className="text-xs text-[#9C8B7A] dark:text-[#C8B9A6]">Customize your viewing experience</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-3 gap-1 bg-[#F3EDE4] dark:bg-[#2C1810] p-1 rounded-xl transition-colors duration-200">
|
||||
{themeOptions.map((opt) => {
|
||||
const active = theme === opt.value;
|
||||
return (
|
||||
<button
|
||||
key={opt.value}
|
||||
onClick={() => setTheme(opt.value)}
|
||||
className={`py-2 px-3 rounded-lg text-xs font-semibold transition-all cursor-pointer ${
|
||||
active
|
||||
? "bg-[#2C1810] text-[#FAF6F1] dark:bg-[#FAF6F1] dark:text-[#2C1810] shadow-sm"
|
||||
: "text-[#6B5744] hover:text-[#2C1810] dark:text-[#C8B9A6] dark:hover:text-[#FAF6F1]"
|
||||
}`}
|
||||
>
|
||||
{opt.label}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* App info */}
|
||||
<div className="mb-6">
|
||||
<div className="text-[11px] font-semibold uppercase tracking-widest text-[#9C8B7A] mb-2 px-1">App</div>
|
||||
<div className="bg-white rounded-2xl border border-[#E8DFD3] overflow-hidden">
|
||||
<div className="flex items-center justify-between px-4 py-3.5 border-b border-[#F3EDE4]">
|
||||
<div className="text-[11px] font-semibold uppercase tracking-widest text-[#9C8B7A] dark:text-[#C8B9A6] mb-2 px-1">App</div>
|
||||
<div className="bg-white dark:bg-[#22120B] rounded-2xl border border-[#E8DFD3] dark:border-[#3B2217] overflow-hidden shadow-sm transition-colors duration-200">
|
||||
<div className="flex items-center justify-between px-4 py-3.5 border-b border-[#F3EDE4] dark:border-[#3B2217]">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-lg">☕</span>
|
||||
<div className="text-sm font-medium text-[#2C1810]">Brew Journal</div>
|
||||
<div className="text-sm font-medium text-[#2C1810] dark:text-[#FAF6F1]">Brew Journal</div>
|
||||
</div>
|
||||
<div className="text-xs text-[#9C8B7A] font-mono">{__APP_VERSION__}</div>
|
||||
<div className="text-xs text-[#9C8B7A] dark:text-[#C8B9A6] font-mono">{__APP_VERSION__}</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 px-4 py-3.5">
|
||||
<span className="text-lg">🗄️</span>
|
||||
<div>
|
||||
<div className="text-xs text-[#9C8B7A]">Storage</div>
|
||||
<div className="text-sm font-medium text-[#2C1810]">Local + PostgreSQL</div>
|
||||
<div className="text-xs text-[#9C8B7A] dark:text-[#C8B9A6]">Storage</div>
|
||||
<div className="text-sm font-medium text-[#2C1810] dark:text-[#FAF6F1]">Local + PostgreSQL</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -65,7 +110,7 @@ export default function ProfilePage({ user, isOnline, syncing, showSyncedStatus
|
||||
{/* Sign out */}
|
||||
<button
|
||||
onClick={() => logout()}
|
||||
className="w-full py-3.5 border border-[#B44040] rounded-2xl text-sm font-semibold text-[#B44040] bg-transparent cursor-pointer hover:bg-[rgba(180,64,64,0.05)] transition-colors">
|
||||
className="w-full py-3.5 border border-[#B44040] dark:border-[#E55B5B] rounded-2xl text-sm font-semibold text-[#B44040] dark:text-[#E55B5B] bg-transparent cursor-pointer hover:bg-[rgba(180,64,64,0.05)] dark:hover:bg-[rgba(229,91,91,0.05)] transition-all">
|
||||
Sign Out
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -36,20 +36,20 @@ export default function Register({ onRegisterSuccess }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="px-5 pt-12 pb-8 max-w-[480px] mx-auto">
|
||||
<div className="px-5 pt-12 pb-8 max-w-[480px] mx-auto transition-colors duration-200">
|
||||
<div className="mb-8 text-center">
|
||||
<div className="text-5xl mb-3">🫘</div>
|
||||
<h1 className="font-serif text-3xl font-semibold text-[#2C1810] mb-1">Create Account</h1>
|
||||
<p className="text-sm text-[#9C8B7A]">Start tracking your coffee journey</p>
|
||||
<h1 className="font-serif text-3xl font-semibold text-[#2C1810] dark:text-[#FAF6F1] mb-1">Create Account</h1>
|
||||
<p className="text-sm text-[#9C8B7A] dark:text-[#C8B9A6]">Start tracking your coffee journey</p>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="bg-[rgba(180,64,64,0.08)] border border-[rgba(180,64,64,0.2)] text-[#B44040] text-sm px-4 py-3 rounded-lg mb-4">
|
||||
<div className="bg-[rgba(180,64,64,0.08)] dark:bg-[rgba(229,91,91,0.08)] border border-[rgba(180,64,64,0.2)] dark:border-[rgba(229,91,91,0.2)] text-[#B44040] dark:text-[#E55B5B] text-sm px-4 py-3 rounded-lg mb-4">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
{success && (
|
||||
<div className="bg-[rgba(74,124,89,0.08)] border border-[rgba(74,124,89,0.2)] text-[#4A7C59] text-sm px-4 py-3 rounded-lg mb-4">
|
||||
<div className="bg-[rgba(74,124,89,0.08)] dark:bg-[rgba(108,178,129,0.08)] border border-[rgba(74,124,89,0.2)] dark:border-[rgba(108,178,129,0.2)] text-[#4A7C59] dark:text-[#6CB281] text-sm px-4 py-3 rounded-lg mb-4">
|
||||
{success}
|
||||
</div>
|
||||
)}
|
||||
@@ -102,7 +102,7 @@ export default function Register({ onRegisterSuccess }) {
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full py-3.5 border-none rounded-lg bg-[#2C1810] text-[#FAF6F1] text-sm font-semibold tracking-wide mt-1 cursor-pointer transition-opacity"
|
||||
className="w-full py-3.5 border-none rounded-lg bg-[#2C1810] dark:bg-[#FAF6F1] text-[#FAF6F1] dark:text-[#2C1810] text-sm font-semibold tracking-wide mt-1 cursor-pointer transition-colors"
|
||||
style={{ opacity: loading ? 0.6 : 1, cursor: loading ? 'not-allowed' : 'pointer' }}
|
||||
>
|
||||
{loading ? 'Creating account…' : 'Create Account'}
|
||||
|
||||
Reference in New Issue
Block a user