Skip to content

Commit ab490fc

Browse files
committed
feature(blog): merge develop
2 parents 7c10419 + a63a90a commit ab490fc

234 files changed

Lines changed: 28470 additions & 33863 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,12 @@ next-env.d.ts
6464
# Documentation (development only)
6565
.claude/
6666
CLAUDE.md
67-
frontend/docs/
67+
frontend/docs/
68+
frontend/.env.bak
69+
70+
71+
# local env backups
72+
frontend/.env*.bak
73+
frontend/.env.bak
74+
75+
tmpclaude-*

frontend/app/[locale]/blog/[slug]/PostDetails.tsx

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,25 +45,41 @@ function plainTextFromPortableText(value: any): string {
4545

4646
const query = groq`
4747
*[_type=="post" && slug.current==$slug][0]{
48+
<<<<<<< HEAD
4849
_id,
4950
"title": coalesce(title[$locale], title[lower($locale)], title.uk, title.en, title.pl, title),
51+
=======
52+
"title": coalesce(title[$locale], title.en, title),
53+
>>>>>>> develop
5054
publishedAt,
5155
"mainImage": mainImage.asset->url,
5256
"categories": categories[]->title,
5357
tags,
5458
resourceLink,
5559
5660
"author": author->{
61+
<<<<<<< HEAD
5762
"name": coalesce(name[$locale], name[lower($locale)], name.uk, name.en, name.pl, name),
5863
"company": coalesce(company[$locale], company[lower($locale)], company.uk, company.en, company.pl, company),
5964
"jobTitle": coalesce(jobTitle[$locale], jobTitle[lower($locale)], jobTitle.uk, jobTitle.en, jobTitle.pl, jobTitle),
6065
"city": coalesce(city[$locale], city[lower($locale)], city.uk, city.en, city.pl, city),
6166
"bio": coalesce(bio[$locale], bio[lower($locale)], bio.uk, bio.en, bio.pl, bio),
67+
=======
68+
"name": coalesce(name[$locale], name.en, name),
69+
"company": coalesce(company[$locale], company.en, company),
70+
"jobTitle": coalesce(jobTitle[$locale], jobTitle.en, jobTitle),
71+
"city": coalesce(city[$locale], city.en, city),
72+
"bio": coalesce(bio[$locale], bio.en, bio),
73+
>>>>>>> develop
6274
"image": image.asset->url,
6375
socialMedia[]{ _key, platform, url }
6476
},
6577
78+
<<<<<<< HEAD
6679
"body": coalesce(body[$locale], body[lower($locale)], body.uk, body.en, body.pl, body)[]{
80+
=======
81+
"body": coalesce(body[$locale], body.en, body)[]{
82+
>>>>>>> develop
6783
...,
6884
_type == "image" => {
6985
...,
@@ -101,13 +117,16 @@ export default async function PostDetails({
101117
slug: slugParam,
102118
locale,
103119
});
120+
<<<<<<< HEAD
104121
const recommendedAll: Post[] = await client.fetch(recommendedQuery, {
105122
slug: slugParam,
106123
locale,
107124
});
108125
const recommendedPosts = recommendedAll
109126
.sort(() => Math.random() - 0.5)
110127
.slice(0, 3);
128+
=======
129+
>>>>>>> develop
111130

112131
if (!post?.title) return notFound();
113132

@@ -249,9 +268,45 @@ export default async function PostDetails({
249268
</>
250269
)}
251270

271+
<<<<<<< HEAD
252272
{post.resourceLink && null}
253273

254274
{(authorBio || authorName || authorMeta) && null}
275+
=======
276+
{(authorBio || authorName || authorMeta) && (
277+
<section className="mt-12 p-6 rounded-2xl border border-gray-200 bg-white">
278+
<h2 className="text-lg font-semibold">{t('aboutAuthor')}</h2>
279+
<div className="mt-4 flex items-start gap-4">
280+
{post.author?.image && (
281+
<div className="relative w-14 h-14 shrink-0">
282+
<Image
283+
src={post.author.image}
284+
alt={authorName || 'Author'}
285+
fill
286+
className="rounded-full object-cover border border-gray-200"
287+
/>
288+
</div>
289+
)}
290+
291+
<div className="min-w-0">
292+
{authorName && (
293+
<p className="text-sm font-semibold text-gray-900">
294+
{authorName}
295+
</p>
296+
)}
297+
{authorMeta && (
298+
<p className="mt-1 text-sm text-gray-600">{authorMeta}</p>
299+
)}
300+
{authorBio && (
301+
<p className="mt-3 text-sm text-gray-700 whitespace-pre-line leading-relaxed">
302+
{authorBio}
303+
</p>
304+
)}
305+
</div>
306+
</div>
307+
</section>
308+
)}
309+
>>>>>>> develop
255310
</main>
256311
);
257312
}

frontend/app/[locale]/blog/page.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,27 +30,43 @@ export default async function BlogPage({
3030
*[_type == "post" && defined(slug.current)]
3131
| order(publishedAt desc) {
3232
_id,
33+
<<<<<<< HEAD
3334
"title": coalesce(title[$locale], title[lower($locale)], title.uk, title.en, title.pl, title),
35+
=======
36+
"title": coalesce(title[$locale], title.en, title),
37+
>>>>>>> develop
3438
slug,
3539
publishedAt,
3640
tags,
3741
resourceLink,
3842
3943
"categories": categories[]->title,
4044
45+
<<<<<<< HEAD
4146
"body": coalesce(body[$locale], body[lower($locale)], body.uk, body.en, body.pl, body)[]{
47+
=======
48+
"body": coalesce(body[$locale], body.en, body)[]{
49+
>>>>>>> develop
4250
...,
4351
children[]{
4452
text
4553
}
4654
},
4755
"mainImage": mainImage.asset->url,
4856
"author": author->{
57+
<<<<<<< HEAD
4958
"name": coalesce(name[$locale], name[lower($locale)], name.uk, name.en, name.pl, name),
5059
"company": coalesce(company[$locale], company[lower($locale)], company.uk, company.en, company.pl, company),
5160
"jobTitle": coalesce(jobTitle[$locale], jobTitle[lower($locale)], jobTitle.uk, jobTitle.en, jobTitle.pl, jobTitle),
5261
"city": coalesce(city[$locale], city[lower($locale)], city.uk, city.en, city.pl, city),
5362
"bio": coalesce(bio[$locale], bio[lower($locale)], bio.uk, bio.en, bio.pl, bio),
63+
=======
64+
"name": coalesce(name[$locale], name.en, name),
65+
"company": coalesce(company[$locale], company.en, company),
66+
"jobTitle": coalesce(jobTitle[$locale], jobTitle.en, jobTitle),
67+
"city": coalesce(city[$locale], city.en, city),
68+
"bio": coalesce(bio[$locale], bio.en, bio),
69+
>>>>>>> develop
5470
"image": image.asset->url,
5571
socialMedia[]{
5672
_key,
@@ -62,6 +78,7 @@ export default async function BlogPage({
6278
`,
6379
{ locale }
6480
);
81+
<<<<<<< HEAD
6582
const categories = await client.fetch(
6683
groq`
6784
*[_type == "category"] | order(orderRank asc) {
@@ -71,6 +88,8 @@ export default async function BlogPage({
7188
`
7289
);
7390
const featuredPost = posts?.[0];
91+
=======
92+
>>>>>>> develop
7493

7594
return (
7695
<main className="max-w-6xl mx-auto px-6 py-12">
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
"use client";
2+
import { Link } from "@/i18n/routing";
3+
import { useState } from "react";
4+
import { useSearchParams } from "next/navigation";
5+
import { Button } from "@/components/ui/button";
6+
7+
export default function ForgotPasswordPage() {
8+
const searchParams = useSearchParams();
9+
const returnTo = searchParams.get("returnTo");
10+
11+
const [loading, setLoading] = useState(false);
12+
const [email, setEmail] = useState("");
13+
const [submitted, setSubmitted] = useState(false);
14+
const [error, setError] = useState<string | null>(null);
15+
16+
async function onSubmit(e: React.FormEvent<HTMLFormElement>) {
17+
e.preventDefault();
18+
setLoading(true);
19+
setError(null);
20+
21+
try {
22+
const res = await fetch("/api/auth/password-reset", {
23+
method: "POST",
24+
headers: { "Content-Type": "application/json" },
25+
body: JSON.stringify({ email }),
26+
});
27+
28+
if (!res.ok) {
29+
setError("Something went wrong. Please try again.");
30+
return;
31+
}
32+
33+
setSubmitted(true);
34+
} catch (err) {
35+
console.error("Password reset request failed:", err);
36+
setError("Network error. Please check your connection and try again.");
37+
} finally {
38+
setLoading(false);
39+
}
40+
}
41+
42+
return (
43+
<div className="mx-auto max-w-sm py-12">
44+
<h1 className="mb-6 text-2xl font-semibold">
45+
Forgot password
46+
</h1>
47+
48+
{submitted ? (
49+
<div className="rounded-md border border-green-400 bg-green-50 p-4 text-sm text-green-800">
50+
<p>
51+
If an account for{" "}
52+
<strong>{email}</strong> exists, we’ve sent a
53+
password reset link.
54+
</p>
55+
56+
<p className="mt-2">
57+
Please check your inbox and follow the
58+
instructions to reset your password.
59+
</p>
60+
61+
<Link
62+
href={
63+
returnTo
64+
? `/login?returnTo=${encodeURIComponent(returnTo)}`
65+
: "/login"
66+
}
67+
className="mt-4 inline-block underline"
68+
>
69+
Back to login
70+
</Link>
71+
</div>
72+
) : (
73+
<form onSubmit={onSubmit} className="space-y-4">
74+
<p className="text-sm text-gray-600">
75+
Enter your email address and we’ll send
76+
you a link to reset your password.
77+
</p>
78+
79+
<input
80+
type="email"
81+
required
82+
placeholder="Email"
83+
value={email}
84+
onChange={e => setEmail(e.target.value)}
85+
className="w-full rounded border px-3 py-2"
86+
/>
87+
88+
{error && (
89+
<p className="text-sm text-red-600">
90+
{error}
91+
</p>
92+
)}
93+
94+
<Button
95+
type="submit"
96+
disabled={loading}
97+
className="w-full"
98+
>
99+
{loading
100+
? "Sending reset link..."
101+
: "Send reset link"}
102+
</Button>
103+
</form>
104+
)}
105+
106+
{!submitted && (
107+
<p className="mt-4 text-sm text-gray-600">
108+
Remembered your password?{" "}
109+
<Link
110+
href={
111+
returnTo
112+
? `/login?returnTo=${encodeURIComponent(returnTo)}`
113+
: "/login"
114+
}
115+
className="underline"
116+
>
117+
Log in
118+
</Link>
119+
</p>
120+
)}
121+
</div>
122+
);
123+
}

0 commit comments

Comments
 (0)