From ca3641e3389258618d557b21126b6e49e66b6bc6 Mon Sep 17 00:00:00 2001
From: dyzulk <66510723+dyzulk@users.noreply.github.com>
Date: Tue, 30 Dec 2025 18:02:36 +0700
Subject: [PATCH] Refactor public legal URLs and fix broken links
---
src/app/(public)/legal/page.tsx | 107 +++++++++++++++++-
src/app/(public)/legal/view/page.tsx | 94 ---------------
src/app/(public)/signin/SigninClient.tsx | 2 +-
src/app/(public)/signup/SignupClient.tsx | 2 +-
.../admin/legal/AdminLegalEditorClient.tsx | 28 +++--
.../admin/legal/AdminLegalListClient.tsx | 2 +-
src/components/Layouts/Public/Footer.tsx | 2 +-
src/hooks/useAuth.ts | 11 ++
src/messages/en.json | 7 ++
9 files changed, 146 insertions(+), 109 deletions(-)
delete mode 100644 src/app/(public)/legal/view/page.tsx
diff --git a/src/app/(public)/legal/page.tsx b/src/app/(public)/legal/page.tsx
index b6bec16..4848eb4 100644
--- a/src/app/(public)/legal/page.tsx
+++ b/src/app/(public)/legal/page.tsx
@@ -1,22 +1,32 @@
"use client";
-import React, { useEffect, useState } from 'react';
+import React, { useEffect, useState, Suspense } from 'react';
import Link from 'next/link';
import { ArrowRight, Scale, FileText } from 'lucide-react';
import axios from '@/lib/axios';
import { useTranslations } from 'next-intl';
+import { useSearchParams } from 'next/navigation';
+import ReactMarkdown from 'react-markdown';
+import remarkGfm from 'remark-gfm';
interface LegalPageItem {
title: string;
slug: string;
}
-export default function LegalIndexPage() {
+function LegalContent() {
const t = useTranslations("Legal");
+ const searchParams = useSearchParams();
+ const pageSlug = searchParams.get('page'); // Changed from 'slug' to 'page' as requested
+
const [pages, setPages] = useState([]);
+ const [activePage, setActivePage] = useState(null);
const [isLoading, setIsLoading] = useState(true);
+ // Fetch List
useEffect(() => {
+ if (pageSlug) return; // Don't fetch list if viewing a page
+
const fetchPages = async () => {
try {
const { data } = await axios.get(`${process.env.NEXT_PUBLIC_API_URL}/api/public/legal-pages`);
@@ -28,8 +38,89 @@ export default function LegalIndexPage() {
}
};
fetchPages();
- }, []);
+ }, [pageSlug]);
+ // Fetch Single Page
+ useEffect(() => {
+ if (!pageSlug) return;
+
+ const fetchPage = async () => {
+ setIsLoading(true);
+ try {
+ const apiUrl = process.env.NEXT_PUBLIC_API_URL;
+ const res = await axios.get(`${apiUrl}/api/public/legal-pages/${pageSlug}`);
+ setActivePage(res.data.data);
+ } catch (error) {
+ console.error(error);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+ fetchPage();
+ }, [pageSlug]);
+
+ // View: Single Page
+ if (pageSlug) {
+ if (isLoading) {
+ return (
+
+ );
+ }
+
+ if (!activePage) {
+ return (
+
+
404 - Not Found
+
+ {t('center_title')}
+
+
+ );
+ }
+
+ return (
+
+
+
+ {t('center_title')}
+
+
+
+
+ {/* Header */}
+
+
+ {activePage.title}
+
+
+ {t('version')} {activePage.version}
+ •
+ {t('last_updated')}: {new Date(activePage.updated_at).toLocaleDateString()}
+
+
+
+ {/* Styled Content */}
+
+ {activePage.content}
+
+
+
+
+ );
+ }
+
+ // View: Index List
return (
{/* Hero Section */}
@@ -62,7 +153,7 @@ export default function LegalIndexPage() {
{pages.map((page) => (
@@ -96,3 +187,11 @@ export default function LegalIndexPage() {
);
}
+
+export default function LegalIndexPage() {
+ return (
+
Loading... }>
+
+
+ )
+}
diff --git a/src/app/(public)/legal/view/page.tsx b/src/app/(public)/legal/view/page.tsx
deleted file mode 100644
index b4d24e7..0000000
--- a/src/app/(public)/legal/view/page.tsx
+++ /dev/null
@@ -1,94 +0,0 @@
-"use client";
-
-import React, { useEffect, useState, Suspense } from 'react';
-import { notFound, useSearchParams } from 'next/navigation';
-import ReactMarkdown from 'react-markdown';
-import remarkGfm from 'remark-gfm';
-import { useTranslations } from 'next-intl';
-
-function LegalPageContent() {
- const t = useTranslations("Legal");
- const searchParams = useSearchParams();
- const slug = searchParams.get('slug');
- const [page, setPage] = useState(null);
- const [loading, setLoading] = useState(true);
-
- useEffect(() => {
- if (!slug) {
- setLoading(false);
- return;
- }
-
- const fetchPage = async () => {
- try {
- const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000';
- const res = await fetch(`${apiUrl}/api/public/legal-pages/${slug}`, {
- headers: { 'Accept': 'application/json' }
- });
- if (res.ok) {
- const json = await res.json();
- setPage(json.data);
- }
- } catch (error) {
- console.error(error);
- } finally {
- setLoading(false);
- }
- };
-
- fetchPage();
- }, [slug]);
-
- if (loading) {
- return (
-
- );
- }
-
- if (!page) {
- return notFound();
- }
-
- return (
-
-
-
- {/* Header */}
-
-
- {page.title}
-
-
- {t('version')} {page.version}
- •
- {t('last_updated')}: {new Date(page.updated_at).toLocaleDateString()}
-
-
-
- {/* Marketing/Styled Content */}
-
- {page.content}
-
-
-
-
- );
-}
-
-export default function PublicLegalPage() {
- return (
- }>
-
-
- );
-}
diff --git a/src/app/(public)/signin/SigninClient.tsx b/src/app/(public)/signin/SigninClient.tsx
index 0246a75..b12f4c8 100644
--- a/src/app/(public)/signin/SigninClient.tsx
+++ b/src/app/(public)/signin/SigninClient.tsx
@@ -241,7 +241,7 @@ export default function SigninClient() {
- {t('agree_to_text')} {t('terms_link')} {t('and_text')} {t('privacy_policy_link')}.
+ {t('agree_to_text')} {t('terms_link')} {t('and_text')} {t('privacy_policy_link')}.
>
diff --git a/src/app/(public)/signup/SignupClient.tsx b/src/app/(public)/signup/SignupClient.tsx
index b0f0bf4..353d4ba 100644
--- a/src/app/(public)/signup/SignupClient.tsx
+++ b/src/app/(public)/signup/SignupClient.tsx
@@ -188,7 +188,7 @@ export default function SignupClient() {
className="mr-3 h-5 w-5 rounded-md border-gray-300 dark:border-gray-700 text-brand-500 focus:ring-brand-500"
/>
- {t('agree_to_signup_text')} {t('terms_link')} {t('and_text')} {t('privacy_policy_link')}.
+ {t('agree_to_signup_text')} {t('terms_link')} {t('and_text')} {t('privacy_policy_link')}.
diff --git a/src/app/dashboard/admin/legal/AdminLegalEditorClient.tsx b/src/app/dashboard/admin/legal/AdminLegalEditorClient.tsx
index 9062035..a00d547 100644
--- a/src/app/dashboard/admin/legal/AdminLegalEditorClient.tsx
+++ b/src/app/dashboard/admin/legal/AdminLegalEditorClient.tsx
@@ -99,7 +99,21 @@ export default function AdminLegalEditorClient({ mode, initialData }: EditorProp
}
router.push("/dashboard/admin/legal");
} catch (error: any) {
- addToast(error.response?.data?.message || t("toast_save_failed"), "error");
+ console.error(error);
+ const msg = error.response?.data?.message;
+ if (msg && msg.length < 150) {
+ addToast(msg, "error");
+ } else {
+ addToast(t("toast_error_generic"), "error");
+ }
+
+ // Handle Validation Errors
+ if (error.response?.status === 422 && error.response?.data?.errors) {
+ // You might want to set form errors state here if you had one
+ // For now just logging or maybe showing first error
+ const firstError = Object.values(error.response.data.errors)[0];
+ if (Array.isArray(firstError)) addToast(firstError[0] as string, "error");
+ }
} finally {
setIsSubmitting(false);
}
@@ -157,11 +171,11 @@ export default function AdminLegalEditorClient({ mode, initialData }: EditorProp
onChange={(e) => setFormData({ ...formData, title: e.target.value })}
>
-
-
-
-
-
+
+
+
+
+
@@ -190,7 +204,7 @@ export default function AdminLegalEditorClient({ mode, initialData }: EditorProp
required
rows={20}
className="w-full px-4 py-4 bg-white dark:bg-gray-900 border-none focus:ring-0 font-mono text-sm text-gray-900 dark:text-gray-300 resize-y"
- placeholder="# Privacy Policy\n\nWrite your content in Markdown..."
+ placeholder={t("placeholder_markdown_content")}
value={formData.content}
onChange={(e) => setFormData({ ...formData, content: e.target.value })}
/>
diff --git a/src/app/dashboard/admin/legal/AdminLegalListClient.tsx b/src/app/dashboard/admin/legal/AdminLegalListClient.tsx
index bb6f6ae..b05a26f 100644
--- a/src/app/dashboard/admin/legal/AdminLegalListClient.tsx
+++ b/src/app/dashboard/admin/legal/AdminLegalListClient.tsx
@@ -109,7 +109,7 @@ export default function AdminLegalListClient() {
(
{page.title}
diff --git a/src/hooks/useAuth.ts b/src/hooks/useAuth.ts
index 18e072c..7e5a258 100644
--- a/src/hooks/useAuth.ts
+++ b/src/hooks/useAuth.ts
@@ -104,6 +104,12 @@ export const useAuth = ({ middleware, redirectIfAuthenticated }: { middleware?:
if (middleware === 'auth' && error) logout();
}, [user, error, middleware, redirectIfAuthenticated, router]);
+ const hasRole = (role: string | string[]) => {
+ if (!user) return false;
+ if (Array.isArray(role)) return role.includes(user.role);
+ return user.role === role;
+ };
+
return {
user,
mutate,
@@ -112,5 +118,10 @@ export const useAuth = ({ middleware, redirectIfAuthenticated }: { middleware?:
logout,
forgotPassword,
resetPassword,
+ hasRole,
+ // Convenience getters
+ isAdmin: user?.role === 'admin',
+ isOwner: user?.role === 'owner',
+ isAdminOrOwner: ['admin', 'owner'].includes(user?.role || ''),
};
};
diff --git a/src/messages/en.json b/src/messages/en.json
index c1dcbba..227b538 100644
--- a/src/messages/en.json
+++ b/src/messages/en.json
@@ -660,6 +660,13 @@
"form_slug_placeholder": "e.g. terms-and-conditions",
"form_content_label": "Document Content (Markdown)",
"form_content_placeholder": "# Privacy Policy\\n\\nWrite your content in Markdown...",
+ "placeholder_markdown_content": "# Privacy Policy\n\nWrite your content in Markdown...",
+ "page_privacy_policy": "Privacy Policy",
+ "page_terms_conditions": "Terms and Conditions",
+ "page_cookie_policy": "Cookie Policy",
+ "page_disclaimer": "Disclaimer",
+ "page_acceptable_use": "Acceptable Use Policy",
+ "toast_error_generic": "An error occurred while saving the page.",
"form_summary_label": "Change Log",
"form_summary_placeholder": "What changed?",
"btn_cancel": "Back to List",
|