Skip to content

Commit 38ca6c6

Browse files
author
Daveed
committed
fixed tailwind config, newsletter page
1 parent 637e7f7 commit 38ca6c6

File tree

6 files changed

+146
-80
lines changed

6 files changed

+146
-80
lines changed

markket.config.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
export const markket = {
3+
store_slug: import.meta.env.PUBLIC_STORE_SLUG,
4+
api_url: import.meta.env.PUBLIC_STRAPI_URL,
5+
sync_interval: 6000,
6+
};
7+

src/components/subscribe-form.tsx

Lines changed: 116 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,33 @@
11

22

3-
import React, { useState, type FormEvent } from 'react';
4-
import { IconRefreshAlert, IconMailbox, IconSquareRoundedX } from '@tabler/icons-react';
5-
import { markket } from '../content/config';
3+
import React, { useState, useRef, useEffect, type FormEvent } from 'react';
4+
import { IconRefreshAlert, IconMailbox, IconSquareRoundedX, IconCheck } from '@tabler/icons-react';
5+
import { markket } from '../../markket.config';
66

77
export interface SubscribeFormProps {
88
store: {
99
documentId: string;
10-
},
10+
};
1111
}
1212

1313
export function SubscribeForm({ store }: SubscribeFormProps) {
1414
const [email, setEmail] = useState('');
1515
const [isSuccess, setIsSuccess] = useState(false);
1616
const [error, setError] = useState('');
1717
const [isSubmitting, setIsSubmitting] = useState(false);
18+
const successRef = useRef<HTMLDivElement | null>(null);
1819

19-
const validateEmail = (email: string) => {
20-
return /^\S+@\S+$/.test(email);
21-
};
20+
useEffect(() => {
21+
if (isSuccess && successRef.current) successRef.current.focus();
22+
}, [isSuccess]);
23+
24+
useEffect(() => {
25+
if (!isSuccess) return;
26+
const t = setTimeout(() => setIsSuccess(false), 6000);
27+
return () => clearTimeout(t);
28+
}, [isSuccess]);
29+
30+
const validateEmail = (value: string) => /^\S+@\S+\.\S+$/.test(value);
2231

2332
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
2433
e.preventDefault();
@@ -36,21 +45,18 @@ export function SubscribeForm({ store }: SubscribeFormProps) {
3645
method: 'POST',
3746
headers: {
3847
'Content-Type': 'application/json',
39-
'Accept': 'application/json',
48+
Accept: 'application/json',
4049
},
4150
body: JSON.stringify({
4251
data: {
4352
Email: email,
4453
stores: store?.documentId ? [store.documentId] : [],
45-
}
54+
},
4655
}),
4756
});
4857

4958
const data = await response.json();
50-
51-
if (!response.ok) {
52-
throw new Error(data.message || 'Subscription failed');
53-
}
59+
if (!response.ok) throw new Error(data?.message || 'Subscription failed');
5460

5561
setIsSuccess(true);
5662
setEmail('');
@@ -63,61 +69,105 @@ export function SubscribeForm({ store }: SubscribeFormProps) {
6369
};
6470

6571
return (
66-
<div className="w-full max-w-md mx-auto">
67-
<form onSubmit={handleSubmit} className="space-y-4">
68-
{error && (
69-
<p className="text-red-600 text-sm font-medium">{error}</p>
70-
)}
71-
72-
<div className="flex gap-3">
73-
<input
74-
type="email"
75-
value={email}
76-
onChange={(e) => setEmail(e.target.value)}
77-
placeholder="your@email.com"
78-
required
79-
disabled={isSubmitting}
80-
className="flex-1 px-4 py-2 rounded-lg border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
81-
/>
82-
<button
83-
type="submit"
84-
disabled={isSubmitting}
85-
className="px-6 py-2 bg-gradient-to-r from-blue-600 to-purple-600 text-white font-medium rounded-lg hover:opacity-90 transition-opacity disabled:opacity-50 disabled:cursor-not-allowed"
86-
>
87-
{isSubmitting ? (
88-
<span className="flex items-center gap-2">
89-
<IconRefreshAlert size={16} />
90-
Subscribing...
91-
</span>
92-
) : (
93-
<span className="flex items-center gap-2">
94-
<IconMailbox size={16} />
95-
Subscribe
96-
</span>
97-
)}
98-
</button>
99-
</div>
100-
</form>
101-
102-
{isSuccess && (
103-
<div className="fixed inset-0 flex items-center justify-center p-4 z-50" style={{ backgroundColor: 'rgba(5, 0, 80, 0.5)' }} onClick={() => setIsSuccess(false)}>
104-
<div className="bg-white rounded-xl p-6 max-w-sm w-full shadow-2xl">
105-
<div className="flex justify-between items-start mb-4">
106-
<h3 className="text-lg font-semibold text-gray-900">Thank you!</h3>
107-
<button
108-
onClick={() => setIsSuccess(false)}
109-
className="text-gray-400 hover:text-gray-500"
110-
>
111-
<span className="sr-only">Close</span>
112-
<IconSquareRoundedX size={24} />
113-
</button>
114-
</div>
115-
<p className="text-gray-600">
116-
You've been successfully subscribed to our newsletter.
117-
</p>
72+
<div className="w-full max-w-4xl mx-auto my-16 px-4">
73+
<div className="relative overflow-hidden rounded-2xl shadow-2xl" style={{ background: 'linear-gradient(180deg, rgba(255,255,255,0.98) 0%, rgba(250,250,255,0.9) 100%)' }}>
74+
<div className="pointer-events-none absolute -right-24 -top-24 w-64 h-64 rounded-full bg-gradient-to-tr from-pink-300 to-indigo-400 opacity-30 blur-3xl" />
75+
76+
<div className="relative grid grid-cols-1 md:grid-cols-2 gap-0">
77+
<div className="p-8 md:p-10 flex flex-col justify-center">
78+
<h3 className="text-2xl md:text-3xl font-extrabold text-gray-900 leading-tight">Join our newsletter</h3>
79+
<p className="mt-2 text-gray-600 max-w-lg">Short, delightful updates about products, design notes, and occasional exclusive offers. No spam — ever.</p>
80+
81+
<ul className="mt-6 space-y-3">
82+
<li className="flex items-start gap-3 text-sm text-gray-700">
83+
<span className="p-2 bg-white rounded-full shadow-sm text-green-600"><IconCheck size={16} /></span>
84+
<span className="leading-tight">Curated product updates and releases</span>
85+
</li>
86+
<li className="flex items-start gap-3 text-sm text-gray-700">
87+
<span className="p-2 bg-white rounded-full shadow-sm text-green-600"><IconCheck size={16} /></span>
88+
<span className="leading-tight">Design insights and short articles</span>
89+
</li>
90+
</ul>
91+
</div>
92+
93+
<div className="p-6 md:p-8 border-l md:border-l border-transparent md:border-l-gray-50 bg-white md:bg-transparent flex items-center">
94+
<form onSubmit={handleSubmit} className="w-full" noValidate>
95+
{error && (
96+
<div id="subscribe-error" role="alert" aria-live="assertive" className="mb-3 text-sm text-red-600 font-medium">
97+
{error}
98+
</div>
99+
)}
100+
101+
<label htmlFor="subscribe-email" className="sr-only">Email address</label>
102+
<div className="flex flex-col gap-3">
103+
<div className="relative w-full">
104+
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400"><IconMailbox size={18} /></span>
105+
<input
106+
id="subscribe-email"
107+
type="email"
108+
value={email}
109+
onChange={(e) => setEmail(e.target.value)}
110+
placeholder="you@yourdomain.com"
111+
required
112+
disabled={isSubmitting}
113+
autoComplete="email"
114+
aria-describedby={error ? 'subscribe-error' : 'subscribe-success'}
115+
className={`w-full pl-11 pr-4 py-4 rounded-2xl border transition-shadow text-base text-gray-900 placeholder-gray-400 bg-white focus:outline-none focus:ring-4 focus:ring-indigo-100 disabled:opacity-60 ${error ? 'border-red-300' : 'border-gray-200'}`}
116+
style={{ minWidth: '20rem' }}
117+
/>
118+
</div>
119+
120+
<div>
121+
<button
122+
type="submit"
123+
disabled={isSubmitting}
124+
className="w-full inline-flex items-center justify-center gap-2 px-6 py-3 bg-gradient-to-r from-indigo-600 via-blue-600 to-purple-600 text-white font-semibold rounded-2xl shadow-md transform transition hover:-translate-y-0.5 disabled:opacity-60"
125+
>
126+
{isSubmitting ? (
127+
<span className="flex items-center gap-2">
128+
<IconRefreshAlert className="animate-spin" size={16} />
129+
Subscribing...
130+
</span>
131+
) : (
132+
<span className="flex items-center gap-2">
133+
<IconMailbox size={16} />
134+
Subscribe
135+
</span>
136+
)}
137+
</button>
138+
</div>
139+
</div>
140+
141+
<p className="mt-3 text-xs text-gray-500">We respect your privacy. Unsubscribe anytime.</p>
142+
143+
{isSuccess && (
144+
<div className="mt-4">
145+
<div
146+
ref={successRef}
147+
tabIndex={-1}
148+
role="status"
149+
aria-live="polite"
150+
id="subscribe-success"
151+
className="p-4 rounded-lg bg-white border border-green-100 text-green-900 shadow-md transform transition-all duration-300"
152+
style={{ boxShadow: '0 10px 30px rgba(2,6,23,0.06)' }}
153+
>
154+
<div className="flex items-start gap-3">
155+
<span className="p-2 rounded-full bg-green-50 text-green-700"><IconCheck size={18} /></span>
156+
<div className="flex-1">
157+
<p className="font-semibold">You're subscribed — thank you!</p>
158+
<p className="text-sm text-green-800/80">We'll send occasional updates to your inbox.</p>
159+
</div>
160+
<button aria-label="Dismiss" className="text-green-700 hover:text-green-900 p-1" onClick={() => setIsSuccess(false)}>
161+
<IconSquareRoundedX size={18} />
162+
</button>
163+
</div>
164+
</div>
165+
</div>
166+
)}
167+
</form>
118168
</div>
119169
</div>
120-
)}
170+
</div>
121171
</div>
122172
);
123173
}

src/content/config.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,6 @@ const config: StrapiConfig = {
1313
sync_interval: 6000,
1414
};
1515

16-
export const markket = {
17-
...config,
18-
};
19-
2016
// @TODO: Find a better type to avoid excessively deep and possibly infinite errors
2117
type Loader = any;
2218

src/pages/newsletter.astro

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,14 @@ const pages = await getCollection("pages");
77
const page = pages.find((p) => p.data.slug == "newsletter")?.data;
88
99
const storeData = await getCollection("store");
10-
const store = storeData[0]?.data || {};
10+
const store = {
11+
documentId: (storeData[0] as any)?.documentId,
12+
...storeData[0]?.data,
13+
};
14+
1115
const storeCover = store.Cover;
16+
17+
import SubscribeForm from "../components/subscribe-form";
1218
---
1319

1420
<PageLayout
@@ -18,8 +24,7 @@ const storeCover = store.Cover;
1824
page?.SEO?.socialImage?.url ||
1925
storeCover?.url}
2026
heroTitle={page?.Title || "Space Newsletter"}
21-
heroSubtitle={page?.SEO?.metaDescription ||
22-
"Sign up for email updates."}
27+
heroSubtitle={page?.SEO?.metaDescription || "Sign up for email updates."}
2328
heroImage={page?.SEO?.socialImage?.url || storeCover?.url}
2429
heroLqip={page?.SEO?.socialImage?.formats?.small?.url}
2530
heroAspect={page?.SEO?.meta?.heroAspect || "4/5"}
@@ -29,6 +34,12 @@ const storeCover = store.Cover;
2934
page?.Content && page.Content.length > 0 && (
3035
<section class="page-content-section">
3136
<div class="container">
37+
<div>
38+
<SubscribeForm
39+
store={{ documentId: store?.documentId }}
40+
client:idle
41+
/>
42+
</div>
3243
<BlocksContent content={page.Content} className="page-content" />
3344
</div>
3445
</section>

src/styles/base.css

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,5 @@
11
@import "tailwindcss";
22

3-
/* Reset and Base Styles */
4-
* {
5-
margin: 0;
6-
padding: 0;
7-
box-sizing: border-box;
8-
}
9-
103
:root {
114
/* Brand Colors - Vibrant and artistic */
125
--primary: #E4007C;

tailwind.config.cjs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
module.exports = {
2+
content: [
3+
'./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}',
4+
],
5+
theme: {
6+
extend: {},
7+
},
8+
plugins: [],
9+
};

0 commit comments

Comments
 (0)