Skip to content

Commit ec244ee

Browse files
authored
Merge pull request #2 from calimania/feat/articles-products
Products, Blog & About index pages
2 parents d754c8f + 38ca6c6 commit ec244ee

File tree

16 files changed

+1301
-403
lines changed

16 files changed

+1301
-403
lines changed

astro.config.mjs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ import { defineConfig } from 'astro/config';
33
import tailwindcss from "@tailwindcss/vite";
44
import react from '@astrojs/react';
55

6+
import sitemap from '@astrojs/sitemap';
7+
68
export default defineConfig({
79
site: 'https://sell.markket.place',
8-
integrations: [react()],
10+
integrations: [react(), sitemap()],
911
vite: {
1012
plugins: [tailwindcss()],
1113
},
12-
});
14+
});

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+

package-lock.json

Lines changed: 57 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
},
1111
"dependencies": {
1212
"@astrojs/react": "^4.4.0",
13+
"@astrojs/sitemap": "^3.6.0",
1314
"@tabler/icons-react": "^3.35.0",
1415
"@tailwindcss/vite": "^4.1.13",
1516
"astro": "^5.13.9",

public/robots.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
User-agent: *
2+
Allow: /
3+
4+
Sitemap: https://sell.markket.place/sitemap-index.xml
5+

src/components/subscribe-form.tsx

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
2+
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';
6+
7+
export interface SubscribeFormProps {
8+
store: {
9+
documentId: string;
10+
};
11+
}
12+
13+
export function SubscribeForm({ store }: SubscribeFormProps) {
14+
const [email, setEmail] = useState('');
15+
const [isSuccess, setIsSuccess] = useState(false);
16+
const [error, setError] = useState('');
17+
const [isSubmitting, setIsSubmitting] = useState(false);
18+
const successRef = useRef<HTMLDivElement | null>(null);
19+
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);
31+
32+
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
33+
e.preventDefault();
34+
setError('');
35+
setIsSubmitting(true);
36+
37+
if (!validateEmail(email)) {
38+
setError('Please enter a valid email address');
39+
setIsSubmitting(false);
40+
return;
41+
}
42+
43+
try {
44+
const response = await fetch(new URL(`/api/subscribers`, markket.api_url), {
45+
method: 'POST',
46+
headers: {
47+
'Content-Type': 'application/json',
48+
Accept: 'application/json',
49+
},
50+
body: JSON.stringify({
51+
data: {
52+
Email: email,
53+
stores: store?.documentId ? [store.documentId] : [],
54+
},
55+
}),
56+
});
57+
58+
const data = await response.json();
59+
if (!response.ok) throw new Error(data?.message || 'Subscription failed');
60+
61+
setIsSuccess(true);
62+
setEmail('');
63+
} catch (err) {
64+
console.error('Subscription error:', err);
65+
setError(err instanceof Error ? err.message : 'Failed to subscribe. Please try again.');
66+
} finally {
67+
setIsSubmitting(false);
68+
}
69+
};
70+
71+
return (
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>
168+
</div>
169+
</div>
170+
</div>
171+
</div>
172+
);
173+
}
174+
175+
export default SubscribeForm;

0 commit comments

Comments
 (0)