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",