Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 24 additions & 12 deletions frontend/app/[locale]/contacts/page.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
export const metadata = {
title: "Contacts | DevLovers",
description:
"Get in touch with the DevLovers team for questions, feedback, or collaboration.",
};
import { getTranslations } from "next-intl/server";

export async function generateMetadata({
params,
}: {
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: "contacts" });

return {
title: t("metaTitle"),
description: t("metaDescription"),
};
}

export default async function ContactsPage() {
const t = await getTranslations("contacts");

export default function ContactsPage() {
return (
<main className="max-w-2xl mx-auto py-12 px-4">
<h1 className="text-3xl font-bold mb-6">Contacts</h1>
<p className="mb-4">We’d love to hear from you! 💬</p>
<h1 className="text-3xl font-bold mb-6">{t("title")}</h1>
<p className="mb-4">{t("subtitle")} 💬</p>
<ul className="space-y-2">
<li>
📧 Email:{" "}
📧 {t("email")}{" "}
<a
href="mailto:victor.svertoka@gmail.com"
className="text-blue-600 hover:underline"
Expand All @@ -20,7 +32,7 @@ export default function ContactsPage() {
</a>
</li>
<li>
💼 LinkedIn:{" "}
💼 {t("linkedin")}{" "}
<a
href="https://www.linkedin.com/in/viktor-svertoka/"
target="_blank"
Expand All @@ -31,7 +43,7 @@ export default function ContactsPage() {
</a>
</li>
<li>
🧑‍💻 GitHub:{" "}
🧑‍💻 {t("github")}{" "}
<a
href="https://github.com/ViktorSvertoka"
target="_blank"
Expand All @@ -44,4 +56,4 @@ export default function ContactsPage() {
</ul>
</main>
);
}
}
30 changes: 21 additions & 9 deletions frontend/app/[locale]/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { redirect } from '@/i18n/routing';
import { Link } from '@/i18n/routing'
import { getTranslations } from 'next-intl/server';
import { getCurrentUser } from '@/lib/auth';
import { getUserProfile } from '@/db/queries/users';
import { getUserQuizStats } from '@/db/queries/quiz';
Expand All @@ -9,10 +10,19 @@ import { StatsCard } from '@/components/dashboard/StatsCard';
import { QuizSavedBanner } from '@/components/dashboard/QuizSavedBanner';
import { PostAuthQuizSync } from "@/components/auth/PostAuthQuizSync";

export const metadata = {
title: 'Dashboard | DevLovers',
description: 'Track your progress and quiz performance.',
};
export async function generateMetadata({
params,
}: {
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: 'dashboard' });

return {
title: t('metaTitle'),
description: t('metaDescription'),
};
}

export default async function DashboardPage({ params }: { params: Promise<{ locale: string }> }) {
const session = await getCurrentUser();
Expand All @@ -22,6 +32,8 @@ export default async function DashboardPage({ params }: { params: Promise<{ loca
const user = await getUserProfile(session.id);
if (!user) { redirect({ href: '/login', locale }); return; }

const t = await getTranslations('dashboard');

const attempts = await getUserQuizStats(session.id);

const totalAttempts = attempts.length;
Expand All @@ -36,7 +48,7 @@ export default async function DashboardPage({ params }: { params: Promise<{ loca

const lastActiveDate =
totalAttempts > 0
? new Date(attempts[0].completedAt).toLocaleDateString('uk-UA')
? new Date(attempts[0].completedAt).toLocaleDateString(locale)
: null;

const userForDisplay = {
Expand Down Expand Up @@ -74,21 +86,21 @@ export default async function DashboardPage({ params }: { params: Promise<{ loca
<div>
<h1 className="text-4xl md:text-5xl font-black tracking-tight drop-shadow-sm">
<span className="bg-gradient-to-r from-sky-400 via-violet-400 to-pink-400 dark:from-sky-400 dark:via-indigo-400 dark:to-fuchsia-500 bg-clip-text text-transparent">
Dashboard
{t('title')}
</span>
</h1>
<p className="mt-2 text-slate-600 dark:text-slate-400 text-lg">
Welcome back to your training ground.
{t('subtitle')}
</p>
</div>

<Link href="/contacts" className={outlineBtnStyles}>
Support & Feedback
{t('supportLink')}
</Link>
</header>
<QuizSavedBanner />
<div className="grid gap-8 md:grid-cols-2">
<ProfileCard user={userForDisplay} />
<ProfileCard user={userForDisplay} locale={locale} />
<StatsCard stats={stats} />
</div>
</div>
Expand Down
7 changes: 6 additions & 1 deletion frontend/components/auth/AuthProvidersBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
"use client";

import { useTranslations } from "next-intl";
import { OAuthButtons } from "@/components/auth/OAuthButtons";

export function AuthProvidersBlock() {
const t = useTranslations("auth");

return (
<>
<OAuthButtons />

<div className="flex items-center gap-3">
<div className="h-px flex-1 bg-gray-200" />
<span className="text-xs text-gray-500">
or
{t("divider")}
</span>
<div className="h-px flex-1 bg-gray-200" />
</div>
Expand Down
20 changes: 8 additions & 12 deletions frontend/components/auth/ForgotPasswordForm.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
"use client";

import { useState } from "react";
import { useTranslations } from "next-intl";
import { Button } from "@/components/ui/button";
import { AuthShell } from "@/components/auth/AuthShell";
import { AuthErrorBanner } from "@/components/auth/AuthErrorBanner";
import { AuthSuccessBanner } from "@/components/auth/AuthSuccessBanner";
import { EmailField } from "@/components/auth/fields/EmailField";

export function ForgotPasswordForm() {
const t = useTranslations("auth.forgotPassword");
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [emailSent, setEmailSent] = useState(false);
Expand Down Expand Up @@ -37,34 +39,30 @@ export function ForgotPasswordForm() {
);

if (!res.ok) {
setError(
"Failed to send reset email. Please try again."
);
setError(t("errors.sendFailed"));
return;
}

setEmailSent(true);
} catch {
setError(
"Network error. Please check your connection."
);
setError(t("errors.networkError"));
} finally {
setLoading(false);
}
}

return (
<AuthShell title="Reset password">
<AuthShell title={t("title")}>
{emailSent ? (
<AuthSuccessBanner
message={
<>
<p>
We’ve sent a password reset link to{" "}
{t("emailSent")}{" "}
<strong>{email}</strong>.
</p>
<p className="mt-2">
Please check your inbox.
{t("checkInbox")}
</p>
</>
}
Expand All @@ -82,9 +80,7 @@ export function ForgotPasswordForm() {
disabled={loading}
className="w-full"
>
{loading
? "Sending..."
: "Send reset link"}
{loading ? t("submitting") : t("submit")}
</Button>
</form>
)}
Expand Down
35 changes: 14 additions & 21 deletions frontend/components/auth/LoginForm.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";

import { useState } from "react";
import { useTranslations } from "next-intl";
import { Link } from "@/i18n/routing";
import { Button } from "@/components/ui/button";
import { AuthShell } from "@/components/auth/AuthShell";
Expand All @@ -20,6 +21,7 @@ export function LoginForm({
locale,
returnTo,
}: LoginFormProps) {
const t = useTranslations("auth.login");
const [loading, setLoading] = useState(false);
const [errorMessage, setErrorMessage] =
useState<string | null>(null);
Expand Down Expand Up @@ -58,11 +60,9 @@ export function LoginForm({
setErrorCode(data?.code ?? null);

if (data?.code === "EMAIL_NOT_VERIFIED") {
setErrorMessage(
"Your email address is not verified. Please check your inbox."
);
setErrorMessage(t("errors.emailNotVerified"));
} else {
setErrorMessage("Invalid email or password");
setErrorMessage(t("errors.invalidCredentials"));
}
return;
}
Expand All @@ -71,9 +71,7 @@ export function LoginForm({
returnTo || `/${locale}/dashboard`;
} catch (err) {
console.error("Login request failed:", err);
setErrorMessage(
"Network error. Please check your connection and try again."
);
setErrorMessage(t("errors.networkError"));
setErrorCode(null);
} finally {
setLoading(false);
Expand All @@ -94,10 +92,7 @@ export function LoginForm({

if (!res.ok) {
setErrorCode(data?.code ?? "RESEND_FAILED");
setErrorMessage(
data?.error ??
"Failed to resend verification email. Please try again."
);
setErrorMessage(data?.error ?? t("errors.resendFailed"));
return;
}

Expand All @@ -107,18 +102,16 @@ export function LoginForm({
} catch (err) {
console.error("Resend verification failed:", err);
setErrorCode("NETWORK_ERROR");
setErrorMessage(
"Network error. Please check your connection and try again."
);
setErrorMessage(t("errors.networkError"));
}
}

return (
<AuthShell
title="Log in"
title={t("title")}
footer={
<p className="text-sm text-gray-600">
Don’t have an account?{" "}
{t("noAccount")}{" "}
<Link
href={
returnTo
Expand All @@ -129,7 +122,7 @@ export function LoginForm({
}
className="underline"
>
Sign up
{t("signupLink")}
</Link>
</p>
}
Expand All @@ -152,7 +145,7 @@ export function LoginForm({
}
className="text-sm underline text-gray-600"
>
Forgot password?
{t("forgotPassword")}
</Link>
</div>

Expand All @@ -161,7 +154,7 @@ export function LoginForm({
message={errorMessage}
actionLabel={
errorCode === "EMAIL_NOT_VERIFIED"
? "Resend verification email"
? t("resendVerification")
: undefined
}
onAction={
Expand All @@ -176,7 +169,7 @@ export function LoginForm({
<AuthSuccessBanner
message={
<>
Verification successfully sent to{" "}
{t("verificationSent")}{" "}
<strong>{email}</strong>
</>
}
Expand All @@ -188,7 +181,7 @@ export function LoginForm({
disabled={loading}
className="w-full"
>
{loading ? "Logging in..." : "Log in"}
{loading ? t("submitting") : t("submit")}
</Button>
</form>
</AuthShell>
Expand Down
Loading