create-nextblock 0.2.51 → 0.2.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/templates/nextblock-template/app/actions/feedback.ts +53 -0
- package/templates/nextblock-template/app/cms/CmsClientLayout.tsx +31 -25
- package/templates/nextblock-template/app/cms/components/FeedbackModal.tsx +134 -0
- package/templates/nextblock-template/next-env.d.ts +1 -1
- package/templates/nextblock-template/package.json +1 -1
package/package.json
CHANGED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use server";
|
|
2
|
+
|
|
3
|
+
import { sendEmail } from "./email";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
interface FeedbackData {
|
|
7
|
+
subject: string;
|
|
8
|
+
message: string;
|
|
9
|
+
userEmail: string;
|
|
10
|
+
userName?: string;
|
|
11
|
+
url?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function submitFeedback(data: FeedbackData) {
|
|
15
|
+
try {
|
|
16
|
+
// If we have an email config, we use it. If not, we might fail or log.
|
|
17
|
+
// sendEmail throws if not configured.
|
|
18
|
+
|
|
19
|
+
const { subject, message, userEmail, userName, url } = data;
|
|
20
|
+
|
|
21
|
+
const htmlContent = `
|
|
22
|
+
<h2>New Feedback Received</h2>
|
|
23
|
+
<p><strong>From:</strong> ${userName || 'Unknown'} (${userEmail})</p>
|
|
24
|
+
<p><strong>Subject:</strong> ${subject}</p>
|
|
25
|
+
<p><strong>URL:</strong> ${url || 'N/A'}</p>
|
|
26
|
+
<br/>
|
|
27
|
+
<h3>Message:</h3>
|
|
28
|
+
<p style="white-space: pre-wrap;">${message}</p>
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
const textContent = `
|
|
32
|
+
New Feedback Received
|
|
33
|
+
From: ${userName || 'Unknown'} (${userEmail})
|
|
34
|
+
Subject: ${subject}
|
|
35
|
+
URL: ${url || 'N/A'}
|
|
36
|
+
|
|
37
|
+
Message:
|
|
38
|
+
${message}
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
await sendEmail({
|
|
42
|
+
to: "feedback@nextblock.ca", // Fixed typo from 'feeedback'
|
|
43
|
+
subject: `[CMS Feedback] ${subject}`,
|
|
44
|
+
text: textContent,
|
|
45
|
+
html: htmlContent,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return { success: true };
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error("Failed to submit feedback:", error);
|
|
51
|
+
return { success: false, error: "Failed to send feedback email." };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -5,15 +5,16 @@ import React, { type ReactNode, useEffect } from "react"
|
|
|
5
5
|
import { useAuth } from "@/context/AuthContext"
|
|
6
6
|
import { useRouter, usePathname } from "next/navigation" // Import usePathname
|
|
7
7
|
import Link from "next/link"
|
|
8
|
-
import {
|
|
9
|
-
LayoutDashboard, FileText, PenTool, Users, Settings, ChevronRight, LogOut, Menu, ListTree, Image as ImageIconLucide, X, Languages as LanguagesIconLucide, MessageSquare,
|
|
10
|
-
Copyright as CopyrightIcon,
|
|
11
|
-
} from "lucide-react"
|
|
12
|
-
import { Button } from "@nextblock-cms/ui"
|
|
13
|
-
import { Avatar, AvatarFallback, AvatarImage } from "@nextblock-cms/ui"
|
|
14
|
-
import { cn } from "@nextblock-cms/utils"
|
|
15
|
-
import { signOutAction } from "@/app/actions";
|
|
16
|
-
import Image from "next/image";
|
|
8
|
+
import {
|
|
9
|
+
LayoutDashboard, FileText, PenTool, Users, Settings, ChevronRight, LogOut, Menu, ListTree, Image as ImageIconLucide, X, Languages as LanguagesIconLucide, MessageSquare,
|
|
10
|
+
Copyright as CopyrightIcon,
|
|
11
|
+
} from "lucide-react"
|
|
12
|
+
import { Button } from "@nextblock-cms/ui"
|
|
13
|
+
import { Avatar, AvatarFallback, AvatarImage } from "@nextblock-cms/ui"
|
|
14
|
+
import { cn } from "@nextblock-cms/utils"
|
|
15
|
+
import { signOutAction } from "@/app/actions";
|
|
16
|
+
import Image from "next/image";
|
|
17
|
+
import { FeedbackModal } from "./components/FeedbackModal";
|
|
17
18
|
|
|
18
19
|
const LoadingSpinner = () => (
|
|
19
20
|
<div className="flex justify-center items-center h-full w-full py-20">
|
|
@@ -211,21 +212,21 @@ export default function CmsClientLayout({ children }: { children: ReactNode }) {
|
|
|
211
212
|
)}
|
|
212
213
|
>
|
|
213
214
|
<div className="flex flex-col h-full">
|
|
214
|
-
<div className="p-4 border-b dark:border-slate-700/60 h-16 flex items-center shrink-0">
|
|
215
|
-
<Link href="/cms/dashboard" className="flex items-center gap-2 px-2">
|
|
216
|
-
<Image
|
|
217
|
-
src="/images/nextblock-logo-small.webp"
|
|
218
|
-
alt="Nextblock logo"
|
|
219
|
-
width={32}
|
|
220
|
-
height={32}
|
|
221
|
-
className="h-8 w-auto"
|
|
222
|
-
priority
|
|
223
|
-
/>
|
|
224
|
-
<h2 className="text-xl font-bold text-foreground">
|
|
225
|
-
Nextblock CMS
|
|
226
|
-
</h2>
|
|
227
|
-
</Link>
|
|
228
|
-
</div>
|
|
215
|
+
<div className="p-4 border-b dark:border-slate-700/60 h-16 flex items-center shrink-0">
|
|
216
|
+
<Link href="/cms/dashboard" className="flex items-center gap-2 px-2">
|
|
217
|
+
<Image
|
|
218
|
+
src="/images/nextblock-logo-small.webp"
|
|
219
|
+
alt="Nextblock logo"
|
|
220
|
+
width={32}
|
|
221
|
+
height={32}
|
|
222
|
+
className="h-8 w-auto"
|
|
223
|
+
priority
|
|
224
|
+
/>
|
|
225
|
+
<h2 className="text-xl font-bold text-foreground">
|
|
226
|
+
Nextblock CMS
|
|
227
|
+
</h2>
|
|
228
|
+
</Link>
|
|
229
|
+
</div>
|
|
229
230
|
|
|
230
231
|
<nav className="px-3 py-4 flex-1 overflow-y-auto">
|
|
231
232
|
<ul className="space-y-1.5">
|
|
@@ -280,6 +281,11 @@ export default function CmsClientLayout({ children }: { children: ReactNode }) {
|
|
|
280
281
|
</ul>
|
|
281
282
|
</nav>
|
|
282
283
|
|
|
284
|
+
|
|
285
|
+
<div className="p-3 pb-0">
|
|
286
|
+
<FeedbackModal />
|
|
287
|
+
</div>
|
|
288
|
+
|
|
283
289
|
<div className="mt-auto p-3 border-t border-slate-200 dark:border-slate-700/60 shrink-0">
|
|
284
290
|
<div className="flex items-center gap-3">
|
|
285
291
|
<Avatar className="h-10 w-10 border">
|
|
@@ -324,4 +330,4 @@ export default function CmsClientLayout({ children }: { children: ReactNode }) {
|
|
|
324
330
|
)}
|
|
325
331
|
</div>
|
|
326
332
|
)
|
|
327
|
-
}
|
|
333
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useState } from "react"
|
|
4
|
+
import { useAuth } from "@/context/AuthContext"
|
|
5
|
+
import { usePathname } from "next/navigation"
|
|
6
|
+
import { toast } from "react-hot-toast"
|
|
7
|
+
import { submitFeedback } from "@/app/actions/feedback"
|
|
8
|
+
import {
|
|
9
|
+
Dialog,
|
|
10
|
+
DialogContent,
|
|
11
|
+
DialogDescription,
|
|
12
|
+
DialogHeader,
|
|
13
|
+
DialogTitle,
|
|
14
|
+
DialogTrigger,
|
|
15
|
+
DialogFooter,
|
|
16
|
+
Button,
|
|
17
|
+
Textarea,
|
|
18
|
+
Label,
|
|
19
|
+
Select,
|
|
20
|
+
SelectContent,
|
|
21
|
+
SelectItem,
|
|
22
|
+
SelectTrigger,
|
|
23
|
+
SelectValue
|
|
24
|
+
} from "@nextblock-cms/ui"
|
|
25
|
+
import { MessageSquarePlus, Loader2 } from "lucide-react"
|
|
26
|
+
import { cn } from "@nextblock-cms/utils"
|
|
27
|
+
|
|
28
|
+
interface FeedbackModalProps {
|
|
29
|
+
sidebarOpen?: boolean
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function FeedbackModal({ sidebarOpen = true }: FeedbackModalProps) {
|
|
33
|
+
const [open, setOpen] = useState(false)
|
|
34
|
+
const [loading, setLoading] = useState(false)
|
|
35
|
+
const [subject, setSubject] = useState("suggestion")
|
|
36
|
+
const [message, setMessage] = useState("")
|
|
37
|
+
const { user, profile } = useAuth()
|
|
38
|
+
const pathname = usePathname()
|
|
39
|
+
|
|
40
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
41
|
+
e.preventDefault()
|
|
42
|
+
if (!message.trim()) {
|
|
43
|
+
toast.error("Please enter a message")
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
setLoading(true)
|
|
48
|
+
try {
|
|
49
|
+
const result = await submitFeedback({
|
|
50
|
+
subject,
|
|
51
|
+
message,
|
|
52
|
+
userEmail: user?.email || "unknown@example.com",
|
|
53
|
+
userName: profile?.full_name || user?.email || "Unknown",
|
|
54
|
+
url: pathname,
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
if (result.success) {
|
|
58
|
+
toast.success("Feedback sent! Thank you.")
|
|
59
|
+
setOpen(false)
|
|
60
|
+
setMessage("")
|
|
61
|
+
setSubject("suggestion")
|
|
62
|
+
} else {
|
|
63
|
+
toast.error("Failed to send feedback. Please try again.")
|
|
64
|
+
}
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error(error)
|
|
67
|
+
toast.error("An error occurred.")
|
|
68
|
+
} finally {
|
|
69
|
+
setLoading(false)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<Dialog open={open} onOpenChange={setOpen}>
|
|
75
|
+
<DialogTrigger asChild>
|
|
76
|
+
<button
|
|
77
|
+
className={cn(
|
|
78
|
+
"flex items-center gap-3 px-3 py-2 rounded-lg text-sm font-medium transition-all w-full text-left",
|
|
79
|
+
"text-slate-600 hover:text-primary hover:bg-primary/5 dark:text-slate-300 dark:hover:bg-primary/10"
|
|
80
|
+
)}
|
|
81
|
+
>
|
|
82
|
+
<MessageSquarePlus className="h-5 w-5" />
|
|
83
|
+
{sidebarOpen && <span>Feedback</span>}
|
|
84
|
+
</button>
|
|
85
|
+
</DialogTrigger>
|
|
86
|
+
<DialogContent className="sm:max-w-[425px]">
|
|
87
|
+
<DialogHeader>
|
|
88
|
+
<DialogTitle>Send Feedback</DialogTitle>
|
|
89
|
+
<DialogDescription>
|
|
90
|
+
Help us improve the CMS. Report bugs or suggest features.
|
|
91
|
+
</DialogDescription>
|
|
92
|
+
</DialogHeader>
|
|
93
|
+
<form onSubmit={handleSubmit} className="grid gap-4 py-4">
|
|
94
|
+
<div className="grid grid-cols-4 items-center gap-4">
|
|
95
|
+
<Label htmlFor="subject" className="text-right">
|
|
96
|
+
Type
|
|
97
|
+
</Label>
|
|
98
|
+
<div className="col-span-3">
|
|
99
|
+
<Select value={subject} onValueChange={setSubject}>
|
|
100
|
+
<SelectTrigger>
|
|
101
|
+
<SelectValue placeholder="Select a subject" />
|
|
102
|
+
</SelectTrigger>
|
|
103
|
+
<SelectContent>
|
|
104
|
+
<SelectItem value="suggestion">Suggestion</SelectItem>
|
|
105
|
+
<SelectItem value="bug">Bug Report</SelectItem>
|
|
106
|
+
<SelectItem value="feature">Feature Request</SelectItem>
|
|
107
|
+
<SelectItem value="other">Other</SelectItem>
|
|
108
|
+
</SelectContent>
|
|
109
|
+
</Select>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
<div className="grid grid-cols-4 items-start gap-4">
|
|
113
|
+
<Label htmlFor="message" className="text-right pt-2">
|
|
114
|
+
Message
|
|
115
|
+
</Label>
|
|
116
|
+
<Textarea
|
|
117
|
+
id="message"
|
|
118
|
+
value={message}
|
|
119
|
+
onChange={(e) => setMessage(e.target.value)}
|
|
120
|
+
placeholder="Tell us what you think..."
|
|
121
|
+
className="col-span-3 min-h-[100px]"
|
|
122
|
+
/>
|
|
123
|
+
</div>
|
|
124
|
+
<DialogFooter>
|
|
125
|
+
<Button type="submit" disabled={loading}>
|
|
126
|
+
{loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
|
127
|
+
Send Feedback
|
|
128
|
+
</Button>
|
|
129
|
+
</DialogFooter>
|
|
130
|
+
</form>
|
|
131
|
+
</DialogContent>
|
|
132
|
+
</Dialog>
|
|
133
|
+
)
|
|
134
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="next" />
|
|
2
2
|
/// <reference types="next/image-types/global" />
|
|
3
|
-
import "./.next/
|
|
3
|
+
import "./.next/types/routes.d.ts";
|
|
4
4
|
|
|
5
5
|
// NOTE: This file should not be edited
|
|
6
6
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|