migrate to tailwindv4

This commit is contained in:
2026-06-06 10:22:12 +05:30
parent 9d700baccc
commit 4f70c77874
7 changed files with 793 additions and 959 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,9 @@
import { useState, useContext } from 'react';
import { AuthContext } from './AuthContext';
const inputCls = "w-full px-3.5 py-3 border border-[#E8DFD3] rounded-lg bg-white font-sans text-sm text-[#2C1810] transition-colors outline-none focus:border-[#8B6914]";
const labelCls = "block text-[10px] font-semibold uppercase tracking-wider text-[#6B5744] mb-1.5";
export default function Login({ onLoginSuccess }) {
const [formData, setFormData] = useState({ email: '', password: '' });
const [error, setError] = useState('');
@@ -11,7 +14,6 @@ export default function Login({ onLoginSuccess }) {
e.preventDefault();
setError('');
setLoading(true);
const result = await login(formData.email, formData.password);
if (result.success) {
onLoginSuccess?.();
@@ -22,41 +24,49 @@ export default function Login({ onLoginSuccess }) {
};
return (
<div className="auth-form" style={{ maxWidth: '400px', margin: '50px auto' }}>
<h2>Login to Brew Journal</h2>
{error && <p style={{ color: '#B44040', marginBottom: '12px' }}>{error}</p>}
<form onSubmit={handleSubmit}>
<input
type="email"
placeholder="Email"
value={formData.email}
onChange={(e) => setFormData({...formData, email: e.target.value})}
required
style={{ marginBottom: '12px', padding: '10px', width: '100%', borderRadius: '8px', border: '1px solid #e8dfd3' }}
/>
<input
type="password"
placeholder="Password"
value={formData.password}
onChange={(e) => setFormData({...formData, password: e.target.value})}
required
style={{ marginBottom: '12px', padding: '10px', width: '100%', borderRadius: '8px', border: '1px solid #e8dfd3' }}
/>
<button
<div className="px-5 pt-12 pb-8 max-w-[480px] mx-auto">
<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>
</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">
{error}
</div>
)}
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
<div>
<label className={labelCls}>Email</label>
<input
className={inputCls}
type="email"
placeholder="you@example.com"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
required
/>
</div>
<div>
<label className={labelCls}>Password</label>
<input
className={inputCls}
type="password"
placeholder="••••••••"
value={formData.password}
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
required
/>
</div>
<button
type="submit"
disabled={loading}
style={{
padding: '10px 16px',
width: '100%',
backgroundColor: '#8B6914',
color: 'white',
border: 'none',
borderRadius: '8px',
cursor: loading ? 'not-allowed' : 'pointer',
opacity: loading ? 0.6 : 1
}}
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"
style={{ opacity: loading ? 0.6 : 1, cursor: loading ? 'not-allowed' : 'pointer' }}
>
{loading ? 'Logging in...' : 'Login'}
{loading ? 'Signing in' : 'Sign In'}
</button>
</form>
</div>

View File

@@ -1,6 +1,9 @@
import { useState, useContext } from 'react';
import { AuthContext } from './AuthContext';
const inputCls = "w-full px-3.5 py-3 border border-[#E8DFD3] rounded-lg bg-white font-sans text-sm text-[#2C1810] transition-colors outline-none focus:border-[#8B6914]";
const labelCls = "block text-[10px] font-semibold uppercase tracking-wider text-[#6B5744] mb-1.5";
export default function Register({ onRegisterSuccess }) {
const [formData, setFormData] = useState({ username: '', email: '', password: '', confirmPassword: '' });
const [error, setError] = useState('');
@@ -17,7 +20,6 @@ export default function Register({ onRegisterSuccess }) {
setError('Passwords do not match');
return;
}
if (formData.password.length < 6) {
setError('Password must be at least 6 characters');
return;
@@ -25,7 +27,6 @@ export default function Register({ onRegisterSuccess }) {
setLoading(true);
const result = await register(formData.username, formData.email, formData.password);
if (result.success) {
setSuccess(result.message);
setFormData({ username: '', email: '', password: '', confirmPassword: '' });
@@ -37,58 +38,76 @@ export default function Register({ onRegisterSuccess }) {
};
return (
<div className="auth-form" style={{ maxWidth: '400px', margin: '50px auto' }}>
<h2>Register for Brew Journal</h2>
{error && <p style={{ color: '#B44040', marginBottom: '12px' }}>{error}</p>}
{success && <p style={{ color: '#4A7C59', marginBottom: '12px' }}>{success}</p>}
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Username"
value={formData.username}
onChange={(e) => setFormData({...formData, username: e.target.value})}
required
style={{ marginBottom: '12px', padding: '10px', width: '100%', borderRadius: '8px', border: '1px solid #e8dfd3' }}
/>
<input
type="email"
placeholder="Email"
value={formData.email}
onChange={(e) => setFormData({...formData, email: e.target.value})}
required
style={{ marginBottom: '12px', padding: '10px', width: '100%', borderRadius: '8px', border: '1px solid #e8dfd3' }}
/>
<input
type="password"
placeholder="Password"
value={formData.password}
onChange={(e) => setFormData({...formData, password: e.target.value})}
required
style={{ marginBottom: '12px', padding: '10px', width: '100%', borderRadius: '8px', border: '1px solid #e8dfd3' }}
/>
<input
type="password"
placeholder="Confirm Password"
value={formData.confirmPassword}
onChange={(e) => setFormData({...formData, confirmPassword: e.target.value})}
required
style={{ marginBottom: '12px', padding: '10px', width: '100%', borderRadius: '8px', border: '1px solid #e8dfd3' }}
/>
<button
<div className="px-5 pt-12 pb-8 max-w-[480px] mx-auto">
<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>
</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">
{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">
{success}
</div>
)}
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
<div>
<label className={labelCls}>Username</label>
<input
className={inputCls}
type="text"
placeholder="coffeelover"
value={formData.username}
onChange={(e) => setFormData({ ...formData, username: e.target.value })}
required
/>
</div>
<div>
<label className={labelCls}>Email</label>
<input
className={inputCls}
type="email"
placeholder="you@example.com"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
required
/>
</div>
<div>
<label className={labelCls}>Password</label>
<input
className={inputCls}
type="password"
placeholder="••••••••"
value={formData.password}
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
required
/>
</div>
<div>
<label className={labelCls}>Confirm Password</label>
<input
className={inputCls}
type="password"
placeholder="••••••••"
value={formData.confirmPassword}
onChange={(e) => setFormData({ ...formData, confirmPassword: e.target.value })}
required
/>
</div>
<button
type="submit"
disabled={loading}
style={{
padding: '10px 16px',
width: '100%',
backgroundColor: '#8B6914',
color: 'white',
border: 'none',
borderRadius: '8px',
cursor: loading ? 'not-allowed' : 'pointer',
opacity: loading ? 0.6 : 1
}}
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"
style={{ opacity: loading ? 0.6 : 1, cursor: loading ? 'not-allowed' : 'pointer' }}
>
{loading ? 'Registering...' : 'Register'}
{loading ? 'Creating account…' : 'Create Account'}
</button>
</form>
</div>

View File

@@ -1,111 +1,114 @@
:root {
--text: #6b6375;
--text-h: #08060d;
--bg: #fff;
--border: #e5e4e7;
--code-bg: #f4f3ec;
--accent: #aa3bff;
--accent-bg: rgba(170, 59, 255, 0.1);
--accent-border: rgba(170, 59, 255, 0.5);
--social-bg: rgba(244, 243, 236, 0.5);
--shadow:
rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px;
@import "tailwindcss";
--sans: system-ui, 'Segoe UI', Roboto, sans-serif;
--heading: system-ui, 'Segoe UI', Roboto, sans-serif;
--mono: ui-monospace, Consolas, monospace;
@theme {
--color-brew-bg: #FAF6F1;
--color-brew-bg2: #F3EDE4;
--color-brew-card: #FFFFFF;
--color-brew-text: #2C1810;
--color-brew-text2: #6B5744;
--color-brew-text3: #9C8B7A;
--color-brew-accent: #8B6914;
--color-brew-accent2: #C4941A;
--color-brew-border: #E8DFD3;
--color-brew-espresso: #5C3317;
--color-brew-coldbrew: #2F4F6F;
--color-brew-pourover: #8B6914;
--color-brew-danger: #B44040;
--color-brew-success: #4A7C59;
font: 18px/145% var(--sans);
letter-spacing: 0.18px;
color-scheme: light dark;
color: var(--text);
background: var(--bg);
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
--font-serif: "Playfair Display", serif;
--font-sans: "Source Sans 3", sans-serif;
@media (max-width: 1024px) {
font-size: 16px;
--radius-sm: 8px;
--radius-md: 12px;
--radius-lg: 20px;
--shadow-card: 0 1px 3px rgba(44,24,16,0.06), 0 4px 12px rgba(44,24,16,0.04);
--shadow-card-lg: 0 4px 16px rgba(44,24,16,0.08), 0 12px 32px rgba(44,24,16,0.06);
}
@layer base {
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
scrollbar-gutter: stable;
font-family: "Source Sans 3", sans-serif;
background-color: #F3EDE4;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
margin: 0;
background-color: #F3EDE4;
}
#root {
width: 100%;
min-height: 100svh;
display: flex;
flex-direction: column;
box-sizing: border-box;
}
input, select, textarea {
font-family: "Source Sans 3", sans-serif;
}
button {
font-family: "Source Sans 3", sans-serif;
}
}
@media (prefers-color-scheme: dark) {
:root {
--text: #9ca3af;
--text-h: #f3f4f6;
--bg: #16171d;
--border: #2e303a;
--code-bg: #1f2028;
--accent: #c084fc;
--accent-bg: rgba(192, 132, 252, 0.15);
--accent-border: rgba(192, 132, 252, 0.5);
--social-bg: rgba(47, 48, 58, 0.5);
--shadow:
rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px;
@layer utilities {
.font-serif {
font-family: "Playfair Display", serif;
}
#social .button-icon {
filter: invert(1) brightness(2);
.font-sans-brew {
font-family: "Source Sans 3", sans-serif;
}
/* Sync badge pulse animation */
.animate-sync-pulse {
animation: sync-pulse 1.5s infinite;
}
@keyframes sync-pulse {
0%, 100% { opacity: 0.3; }
50% { opacity: 1; }
}
/* Modal animations */
.animate-fade-in {
animation: fade-in 0.2s ease-out;
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
.animate-slide-up {
animation: slide-up 0.3s ease-out;
}
@keyframes slide-up {
from { transform: translateY(100%); }
to { transform: translateY(0); }
}
/* Brew method bar */
.brew-method-bar {
position: absolute;
left: 0;
top: 12px;
bottom: 12px;
width: 4px;
border-radius: 0 4px 4px 0;
}
}
body {
margin: 0;
}
#root {
width: 1126px;
max-width: 100%;
margin: 0 auto;
text-align: center;
border-inline: 1px solid var(--border);
min-height: 100svh;
display: flex;
flex-direction: column;
box-sizing: border-box;
}
h1,
h2 {
font-family: var(--heading);
font-weight: 500;
color: var(--text-h);
}
h1 {
font-size: 56px;
letter-spacing: -1.68px;
margin: 32px 0;
@media (max-width: 1024px) {
font-size: 36px;
margin: 20px 0;
}
}
h2 {
font-size: 24px;
line-height: 118%;
letter-spacing: -0.24px;
margin: 0 0 8px;
@media (max-width: 1024px) {
font-size: 20px;
}
}
p {
margin: 0;
}
code,
.counter {
font-family: var(--mono);
display: inline-flex;
border-radius: 4px;
color: var(--text-h);
}
code {
font-size: 15px;
line-height: 135%;
padding: 4px 8px;
background: var(--code-bg);
}