Skip to content
Merged

Dev #67

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
2 changes: 1 addition & 1 deletion src/hooks.server.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { redirect } from '@sveltejs/kit';
export async function handle({ event, resolve }) {
const sessionId = await event.cookies.get('session');
// console.log(sessionId, '-----------------sessionid')
let user = false
let user = null;
if(sessionId && sessionId!=''){
user = await prisma.user.findFirst({
where: {
Expand Down
6 changes: 3 additions & 3 deletions src/lib/newsletter.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Newsletter utility functions for email confirmation and management

/**
* Generate unsubscribe link for newsletter
* Generate unsubscribe link
* @param {string} token - Confirmation token
* @param {string} baseUrl - Base URL of the application
* @returns {string} Unsubscribe URL
Expand Down Expand Up @@ -95,14 +95,14 @@ export function generateWelcomeEmail(email, unsubscribeLink) {

/**
* Generate newsletter template for regular updates
* @param {object} content - Newsletter content
* @param {any} content - Newsletter content
* @param {string} unsubscribeLink - Unsubscribe link
* @returns {object} Newsletter template with subject and body
*/
export function generateNewsletterTemplate(content, unsubscribeLink) {
const { subject, headline, articles = [], ctaText = 'Learn More', ctaLink = 'https://bottlecrm.io' } = content;

const articlesHtml = articles.map(article => `
const articlesHtml = articles.map(/** @param {any} article */ article => `
<div style="background: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; border-left: 4px solid #667eea;">
<h3 style="margin: 0 0 10px 0; color: #333;">${article.title}</h3>
<p style="margin: 0 0 15px 0; color: #666; line-height: 1.6;">${article.excerpt}</p>
Expand Down
3 changes: 3 additions & 0 deletions src/lib/stores/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ export const auth = writable({
});

// Helper to get the current session user from event.locals (SvelteKit convention)
/**
* @param {any} event
*/
export function getSessionUser(event) {
// If you use event.locals.user for authentication, return it
// You can adjust this logic if your user is stored differently
Expand Down
7 changes: 6 additions & 1 deletion src/routes/(admin)/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@
import '../../app.css'
import { Menu, Bell, User, Search, FileText, Settings, ChartBar, Home, X, LogOut } from '@lucide/svelte';

/** @type {{ data: import('./admin/$types').LayoutData, children: import('svelte').Snippet }} */
/** @type {{ data?: any, children: import('svelte').Snippet }} */
let { data, children } = $props();

let mobileMenuOpen = $state(false);

const handleLogout = () => {
// Perform logout action - you might want to redirect to logout endpoint
window.location.href = '/logout';
};

</script>

<div class="min-h-screen bg-gray-50">
Expand Down
2 changes: 1 addition & 1 deletion src/routes/(admin)/admin/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
const { metrics } = data;

// Format numbers with commas
const formatNumber = (num) => {
const formatNumber = (/** @type {any} */ num) => {
return new Intl.NumberFormat('en-US').format(num);
};
</script>
Expand Down
2 changes: 1 addition & 1 deletion src/routes/(admin)/admin/blogs/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
{#each data.blogs as blog}
<tr class="hover:bg-gray-50">
<td class="py-2 px-4 border-b"><a href="/admin/blogs/{blog.id}/">{blog.title}</a> - <a href="/admin/blogs/{blog.id}/edit/">Edit</a></td>
<td class="py-2 px-4 border-b">{blog.category}</td>
<td class="py-2 px-4 border-b">N/A</td>
<td class="py-2 px-4 border-b">
{#if blog.draft}
<span class="inline-block px-2 py-1 text-xs font-semibold text-yellow-800 bg-yellow-100 rounded">Draft</span>
Expand Down
40 changes: 18 additions & 22 deletions src/routes/(admin)/admin/blogs/[id]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
<div class="flex justify-between items-center">
<nav class="text-sm text-gray-500 dark:text-gray-400">
<span>Admin</span> <span class="mx-2">/</span> <span>Blogs</span> <span class="mx-2">/</span> <span class="text-gray-900 dark:text-white">{data.blog.title}</span>
<span>Admin</span> <span class="mx-2">/</span> <span>Blogs</span> <span class="mx-2">/</span> <span class="text-gray-900 dark:text-white">{data.blog?.title || 'Blog'}</span>
</nav>
<a
href={`/admin/blogs/${data.blog.id}/edit`}
href={`/admin/blogs/${data.blog?.id}/edit`}
class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-blue-600 hover:bg-blue-700 text-white font-medium text-sm transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900"
>
<Edit size={16} />
Expand All @@ -35,15 +35,12 @@
<!-- Blog Header -->
<header class="text-center mb-12">
<div class="mb-4">
<a
href="/blog/category/{data.blog.categorySlug}"
class="inline-block px-3 py-1 rounded-full bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200 font-medium text-sm hover:bg-yellow-200 dark:hover:bg-yellow-800 transition-colors"
>
{data.blog.category}
</a>
<span class="inline-block px-3 py-1 rounded-full bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200 font-medium text-sm">
Blog Post
</span>
</div>
<h1 class="text-3xl md:text-4xl lg:text-5xl font-bold text-gray-900 dark:text-white mb-6 leading-tight">
{data.blog.title}
{data.blog?.title || 'Untitled'}
</h1>
</header>

Expand All @@ -52,7 +49,7 @@
<!-- Main Content Column -->
<article class="lg:col-span-8 xl:col-span-9">
<div class="prose prose-lg dark:prose-invert max-w-none">
{#each data.blog.contentBlocks as block}
{#each data.blog?.contentBlocks || [] as block}
<div class="mb-8">
{#if block.type == "MARKDOWN"}
<div class="text-gray-800 dark:text-gray-200 leading-relaxed">
Expand Down Expand Up @@ -118,8 +115,8 @@
<h3 class="font-semibold text-gray-900 dark:text-white mb-4">Blog Details</h3>
<div class="space-y-3 text-sm">
<div>
<span class="text-gray-600 dark:text-gray-400">Category:</span>
<span class="ml-2 text-gray-900 dark:text-white">{data.blog.category}</span>
<span class="text-gray-600 dark:text-gray-400">Type:</span>
<span class="ml-2 text-gray-900 dark:text-white">Blog Post</span>
</div>
<div>
<span class="text-gray-600 dark:text-gray-400">Status:</span>
Expand All @@ -134,14 +131,14 @@
</div>

<MetaTags
title={data.blog.seoTitle}
title={data.blog?.seoTitle || 'Blog Post'}
titleTemplate="%s | BottleCRM"
description={data.blog.seoDescription}
canonical={"https://bottlecrm.com/blog/" + data.blog.slug + "/"}
description={data.blog?.seoDescription || 'Blog post'}
canonical={"https://bottlecrm.com/blog/" + (data.blog?.slug || '') + "/"}
openGraph={{
url: data.blog.slug,
title: data.blog.seoTitle,
description: data.blog.seoDescription,
url: data.blog?.slug || '',
title: data.blog?.seoTitle || 'Blog Post',
description: data.blog?.seoDescription || 'Blog post',
images: [
{
url: "https://bottlecrm.com/images/logo.png",
Expand All @@ -150,14 +147,13 @@
alt: "BottleCRM - Open Source CRM Solution",
},
],
site_name: "BottleCRM",
siteName: "BottleCRM",
}}
twitter={{
handle: "@bottlecrm",
site: "@bottlecrm",
cardType: "summary_large_image",
title: data.blog.seoTitle,
description: data.blog.seoDescription,
title: data.blog?.seoTitle || 'Blog Post',
description: data.blog?.seoDescription || 'Blog post',
image: "https://bottlecrm.com/images/logo.png",
imageAlt: "BottleCRM - Open Source CRM Solution",
}}
Expand Down
54 changes: 41 additions & 13 deletions src/routes/(admin)/admin/blogs/[id]/edit/+page.server.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,44 +21,66 @@ export const actions = {

'add-block': async ({ request, params }) => {
const form = await request.formData();
const type = form.get('type')?.toString();
const content = form.get('content')?.toString();
const displayOrder = form.get('displayOrder')?.toString();

if (!type || !content || !displayOrder) {
return { success: false, error: 'Missing required fields' };
}

await prisma.blogContentBlock.create({
data: {
blogId: params.id,
type: form.get('type'),
content: form.get('content'),
displayOrder: Number(form.get('displayOrder')),
type: /** @type {import('@prisma/client').ContentBlockType} */ (type),
content: content,
displayOrder: Number(displayOrder),
draft: form.get('draft') === 'on'
}
});
return { success: true };
},
'edit-block': async ({ request }) => {
const form = await request.formData();
const id = form.get('id')?.toString();
const type = form.get('type')?.toString();
const content = form.get('content')?.toString();

if (!id || !type || !content) {
return { success: false, error: 'Missing required fields' };
}

await prisma.blogContentBlock.update({
where: { id: form.get('id') },
where: { id: id },
data: {
type: form.get('type'),
content: form.get('content'),
type: /** @type {import('@prisma/client').ContentBlockType} */ (type),
content: content,
draft: form.get('draft') === 'on'
}
});
return { success: true };
},
'delete-block': async ({ request }) => {
const form = await request.formData();
const id = form.get('id')?.toString();

if (!id) {
return { success: false, error: 'Missing block ID' };
}

await prisma.blogContentBlock.delete({
where: { id: form.get('id') }
where: { id: id }
});
return { success: true };
},
'update-blog': async ({ request, params }) => {
const form = await request.formData();
const data = {
title: form.get('title'),
seoTitle: form.get('seoTitle'),
seoDescription: form.get('seoDescription'),
excerpt: form.get('excerpt'),
slug: form.get('slug'),
title: form.get('title')?.toString() || '',
seoTitle: form.get('seoTitle')?.toString() || '',
seoDescription: form.get('seoDescription')?.toString() || '',
excerpt: form.get('excerpt')?.toString() || '',
slug: form.get('slug')?.toString() || '',
draft: form.get('draft') === 'on'
};
await prisma.blogPost.update({
Expand All @@ -70,7 +92,13 @@ export const actions = {
,
'reorder-blocks': async ({ request, params }) => {
const form = await request.formData();
const order = JSON.parse(form.get('order'));
const orderStr = form.get('order')?.toString();

if (!orderStr) {
return { success: false, error: 'Missing order data' };
}

const order = JSON.parse(orderStr);
for (const { id, displayOrder } of order) {
await prisma.blogContentBlock.update({
where: { id },
Expand Down
19 changes: 10 additions & 9 deletions src/routes/(admin)/admin/blogs/[id]/edit/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@
import { dndzone } from "svelte-dnd-action";
/** @type {{ data: import('./$types').PageData }} */
export let data;
export let form;
/** @type {any} */
let blog = data.blog;
let blog = /** @type {any} */ (data)?.blog || {};
/** @type {any[]} */
let contentBlocks = blog.contentBlocks
let contentBlocks = blog?.contentBlocks || []

// Drag and drop handler for reordering content blocks
async function handleReorder({ detail }) {
async function handleReorder(/** @type {any} */ { detail }) {
// detail.items is the new order of contentBlocks
// Reorder contentBlocks array to match the new order from dndzone
contentBlocks = detail.items.map((item, idx) => ({
contentBlocks = detail.items.map((/** @type {any} */ item, /** @type {any} */ idx) => ({
...item,
displayOrder: idx + 1
}));
Expand All @@ -29,6 +28,7 @@
let message = "";

// For editing/adding content blocks
/** @type {any} */
let editingBlockId = null;
let newBlock = {
type: "MARKDOWN",
Expand All @@ -37,14 +37,14 @@
draft: false,
};

function startEditBlock(block) {
function startEditBlock(/** @type {any} */ block) {
editingBlockId = block.id;
/** @type {any} */ (block)._editContent = block.content;
/** @type {any} */ (block)._editType = block.type;
/** @type {any} */ (block)._editDraft = block.draft;
}

function cancelEditBlock(block) {
function cancelEditBlock(/** @type {any} */ block) {
editingBlockId = null;
delete block._editContent;
delete block._editType;
Expand All @@ -61,12 +61,13 @@
.replace(/\s+/g, '-')
.replace(/[^\w-]+/g, '');
}
let editable_title = form?.data?.title ?? blog.title;
let slug = form?.data?.slug ?? blog.slug;
let editable_title = blog?.title || '';
let slug = blog?.slug || '';


// Initialize previous_editable_title_for_slug_generation to undefined
// so we can detect the first run of the reactive block.
/** @type {any} */
let previous_editable_title_for_slug_generation = undefined;

$: {
Expand Down
14 changes: 7 additions & 7 deletions src/routes/(admin)/admin/blogs/new/+page.server.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,27 @@ export async function load() {
export const actions = {
default: async ({ request }) => {
const data = await request.formData();
const title = data.get('title');
const excerpt = data.get('excerpt');
const slug = data.get('slug');
const title = data.get('title')?.toString();
const excerpt = data.get('excerpt')?.toString();
const slug = data.get('slug')?.toString();

if (!title || !excerpt || !slug) {
return { error: 'All fields are required' };
}
try {
await prisma.blogPost.create({
data: {
title,
excerpt,
slug,
title: title,
excerpt: excerpt,
slug: slug,
seoTitle:"",
seoDescription: "",
draft: true
}
});
return { success: true };
} catch (e) {
return { error: e?.message || 'Error creating blog' };
return { error: /** @type {any} */ (e)?.message || 'Error creating blog' };
}
}
};
6 changes: 3 additions & 3 deletions src/routes/(admin)/admin/blogs/new/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
/** @type {import('./$types').PageProps} */
let { form } = $props();

let title = $state(form?.data?.title ?? '');
let excerpt = $state(form?.data?.excerpt ?? '');
let title = $state('');
let excerpt = $state('');

function make_slug(title) {
function make_slug(/** @type {any} */ title) {
return title
.toLowerCase()
.replace(/\s+/g, '-')
Expand Down
1 change: 1 addition & 0 deletions src/routes/(admin)/admin/contacts/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/** @type {{ data: import('./$types').PageData }} */
let { data } = $props();

/** @param {string | Date} dateString */
const formatDate = (dateString) => {
return new Date(dateString).toLocaleDateString('en-US', {
year: 'numeric',
Expand Down
Loading