Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
d020e37
Merge pull request #7 from knovator/development
chavda-bhavik Sep 2, 2022
4629f1a
Merge pull request #9 from knovator/development
chavda-bhavik Sep 2, 2022
044f190
Merge pull request #12 from knovator/development
chavda-bhavik Sep 12, 2022
c3924df
Merge pull request #14 from knovator/development
chavda-bhavik Sep 12, 2022
7c96204
Merge pull request #16 from knovator/development
chavda-bhavik Sep 13, 2022
d5ec8f2
Merge pull request #18 from knovator/development
chavda-bhavik Sep 13, 2022
72c4623
Merge pull request #20 from knovator/development
chavda-bhavik Sep 14, 2022
31bbd2b
Merge pull request #22 from knovator/development
chavda-bhavik Sep 15, 2022
55ee870
Merge pull request #24 from knovator/development
chavda-bhavik Sep 15, 2022
959c9f6
Merge pull request #27 from knovator/development
chavda-bhavik Sep 16, 2022
2778161
Merge pull request #29 from knovator/development
chavda-bhavik Sep 16, 2022
7e405ad
Merge pull request #32 from knovator/development
chavda-bhavik Sep 19, 2022
0cd83b7
Merge pull request #34 from knovator/development
chavda-bhavik Sep 20, 2022
7b261da
Merge pull request #36 from knovator/development
chavda-bhavik Sep 20, 2022
12191ca
Merge pull request #38 from knovator/development
chavda-bhavik Sep 20, 2022
9c45cf5
Merge pull request #40 from knovator/development
chavda-bhavik Sep 22, 2022
ecba4ba
Merge pull request #42 from knovator/development
chavda-bhavik Sep 22, 2022
39f0b9c
Merge pull request #45 from knovator/development
chavda-bhavik Sep 28, 2022
0d9444a
Merge pull request #48 from knovator/development
chavda-bhavik Oct 10, 2022
0aa4c83
Merge pull request #52 from knovator/development
chavda-bhavik Dec 12, 2022
d913bca
add filter icon and it's event handler
hardikgami007 Jan 15, 2026
e43b19b
Merge branch 'development' of https://github.com/knovator/pagecreator…
hardikgami007 Jan 15, 2026
dcee682
add filter icon and it's event handler
hardikgami007 Jan 15, 2026
9f76eb3
Merge branch 'development' of https://github.com/knovator/pagecreator…
hardikgami007 Jan 15, 2026
6e9cbe1
lock file change
hardikgami007 Jan 15, 2026
d6ca65d
change for custom page
hardikgami007 Jan 15, 2026
93e762b
add data in props
hardikgami007 Jan 16, 2026
ba047eb
pass the data in edit and make setting icon disable
hardikgami007 Jan 27, 2026
9542a58
change translation
hardikgami007 Jan 29, 2026
43c7abb
change the data of widget
hardikgami007 Jan 29, 2026
606df0f
package change
hardikgami007 Jan 30, 2026
c1b5ee2
Merge branch 'development' of https://github.com/knovator/pagecreator…
hardikgami007 Jan 30, 2026
e7e519e
change for single mongodb
hardikgami007 Feb 3, 2026
90463d9
Merge remote feature/single_db
hardikgami007 Feb 3, 2026
b8fa3f7
change for singledb
hardikgami007 Feb 3, 2026
152b009
done flow
hardikgami007 Feb 9, 2026
da9f8c7
interlinking
hardikgami007 Feb 10, 2026
4faba9c
feat: Implement full-stack widget management with admin UI components…
hardikgami007 Feb 10, 2026
db93664
chnage aggregation
hardikgami007 Feb 11, 2026
f93229e
feat: Implement widget management with a new admin form supporting bl…
hardikgami007 Feb 11, 2026
1ba9c44
user side change
hardikgami007 Feb 13, 2026
456f73b
fix issue
hardikgami007 Feb 17, 2026
2179d86
Merge pull request #164 from knovator/feature/interlinking
chavda-bhavik Feb 19, 2026
96fba76
Merge branch 'feature/custom_browse_jobs' of https://github.com/knova…
hardikgami007 Feb 19, 2026
567b8dd
change package version
hardikgami007 Feb 19, 2026
8cff44c
Merge pull request #163 from knovator/feature/single_db
chavda-bhavik Feb 19, 2026
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
7 changes: 7 additions & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"permissions": {
"allow": [
"Bash(npx nx build:*)"
]
}
}
71 changes: 71 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# PageCreator - Project Guide

## Project Overview
PageCreator is an NX monorepo CMS library for creating widget-based pages. It provides admin UI, user-facing rendering components, and a backend with models/controllers/routes.

## Architecture

### Monorepo Structure
```
pagecreator/
├── apps/
│ ├── api/ # Express backend (demo server)
│ ├── pagecreator/ # Admin UI app (React/Vite)
│ ├── front/ # Public frontend (Next.js)
├── libs/
│ ├── admin/ # Admin UI library (components, contexts, forms)
│ ├── user/ # User library (widget rendering components)
│ └── node/ # Backend library (models, services, controllers, routes)
```

### Tech Stack
- **Backend**: Express, MongoDB (Mongoose), Redis (caching)
- **Admin**: React 18, React Hook Form, React Beautiful DnD, React Select
- **User**: React 18, Swiper (carousel), React Tabs
- **Build**: NX, TypeScript

### Library Exports
- `@nichekit/node` → Backend models, routes, controllers
- `@nichekit/admin` → Admin UI components (Widget, Page, Provider)
- `@nichekit/user` → User components (Widget, Page, getData)

## Data Model
- **Page**: name, code, slug, widgets[] (Widget refs)
- **Widget**: name, code, widgetType, itemsType, items, tabs, collectionItems, layout config
- **Item**: title, subtitle, altText, link, img, srcset, itemType (Web/Mobile)
- **Tab**: name, widgetId, collectionItems[]
- **SrcSet**: width, height, screenSize, itemId

### Widget Types: FixedCard, Carousel, Tabs, Text, HTML
### Item Types: Image (built-in), plus external collections via setConfig()

## Key Patterns
- Config via `setConfig()`: collections, customWidgetTypes, languages, redis
- Redis caching: `widgetData_${code}`, `pageData_${code}`
- Admin state: React Context (WidgetContext, PageContext, ProviderContext)
- User lib: Props-based with callbacks (formatItem, onClick, formatHeader, formatFooter)
- CSS prefixes: `khb_` (admin), `kpc_` (user)
- Soft delete, unique code validation, multi-language support

## Key Files
| Area | Path |
|------|------|
| Enums | `libs/node/src/types/enums.ts` |
| Types | `libs/node/src/types/common.ts` |
| Widget Model | `libs/node/src/models/Widget.ts` |
| Widget Controller | `libs/node/src/controllers/WidgetController.ts` |
| Data Service | `libs/node/src/services/dataService.ts` |
| Widget Form (Admin) | `libs/admin/src/lib/components/Widget/Form/WidgetForm.tsx` |
| Widget Context | `libs/admin/src/lib/context/WidgetContext.tsx` |
| Widget Router (User) | `libs/user/src/lib/components/widget/widget.tsx` |
| Page Component | `libs/user/src/lib/components/page/page.tsx` |
| User Types | `libs/user/src/lib/types/api.ts` |

## Build Commands
```bash
npx nx build node # Backend library
npx nx build admin # Admin library
npx nx build user # User library
npx nx serve api # API server
npx nx serve pagecreator # Admin app
```
143 changes: 142 additions & 1 deletion apps/api/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import './db/db';
import './models/notification';
import Blog from './models/blog';
import BlogCategory from './models/blogCategory';
import express from 'express';
import cors from 'cors';
import path from 'path';
Expand Down Expand Up @@ -34,6 +36,12 @@ setConfig({
collectionName: 'project_assessment',
searchColumns: ['assessmentNm', 'projectNm'],
},
{
title: 'Blogs',
collectionName: 'blog',
searchColumns: ['title', 'name', 'slug'],
match: { isPublished: true, isActive: true },
},
],
// redis: {
// HOST: 'localhost',
Expand Down Expand Up @@ -66,8 +74,141 @@ app.get('/delete', (req, res) => {
app.use(resize(path.join(__dirname, 'public')));
app.use(express.static(path.join(__dirname, './public')));

// Seed test blogs
async function seedBlogs() {
try {
const count = await Blog.countDocuments();
if (count === 0) {
await Blog.insertMany([
{
title: 'Getting Started with React',
value: '123123123123',
name: 'Getting Started with React',
description: 'A beginner guide to building user interfaces with React.',
slug: 'getting-started-with-react',
coverImage: '/image/react_intro.png',
isPublished: true,
isActive: true,
isDeleted: false,
publishedAt: new Date('2025-08-10T10:00:00.000Z'),
viewCount: 250,
author: { id: '68c2d1d8ffb1adbf30004ded', nm: 'Ragnar Lothbrok', isActive: true },
category: [{ id: '68c92a0426291fe3408a8141', nm: 'Technology', slug: 'technology', isActive: true }],
},
{
title: 'Understanding MongoDB Aggregations',
value: '123123123123234',
name: 'Understanding MongoDB Aggregations',
description: 'Deep dive into MongoDB aggregation pipelines and their use cases.',
slug: 'understanding-mongodb-aggregations',
coverImage: '/image/mongodb_agg.png',
isPublished: true,
isActive: true,
isDeleted: false,
publishedAt: new Date('2025-09-01T08:30:00.000Z'),
viewCount: 180,
author: { id: '68c2d1d8ffb1adbf30004ded', nm: 'Ragnar Lothbrok', isActive: true },
category: [{ id: '68c92a0426291fe3408a8142', nm: 'Database', slug: 'database', isActive: true }],
},
{
title: 'Building REST APIs with Express',
value: '123123123123234234',
name: 'Building REST APIs with Express',
description: 'Learn how to create robust REST APIs using Express.js.',
slug: 'building-rest-apis-with-express',
coverImage: '/image/express_api.png',
isPublished: true,
isActive: true,
isDeleted: false,
publishedAt: new Date('2025-09-10T14:00:00.000Z'),
viewCount: 320,
author: { id: '68c2d1d8ffb1adbf30004ded', nm: 'Ragnar Lothbrok', isActive: true },
category: [{ id: '68c92a0426291fe3408a8141', nm: 'Technology', slug: 'technology', isActive: true }],
},
{
title: 'CSS Grid vs Flexbox',
value: '123123123123234234234234',
name: 'CSS Grid vs Flexbox',
description: 'Comparing CSS Grid and Flexbox for modern web layouts.',
slug: 'css-grid-vs-flexbox',
coverImage: '/image/css_layout.png',
isPublished: true,
isActive: true,
isDeleted: false,
publishedAt: new Date('2025-09-15T12:00:00.000Z'),
viewCount: 95,
author: { id: '68c2d1d8ffb1adbf30004ded', nm: 'Ragnar Lothbrok', isActive: true },
category: [{ id: '68c92a0426291fe3408a8143', nm: 'Design', slug: 'design', isActive: true }],
},
{
title: 'TypeScript Best Practices',
name: 'TypeScript Best Practices',
description: 'Tips and patterns for writing clean TypeScript code.',
slug: 'typescript-best-practices',
coverImage: '/image/typescript_tips.png',
isPublished: true,
isActive: true,
isDeleted: false,
publishedAt: new Date('2025-09-16T09:12:50.750Z'),
viewCount: 410,
author: { id: '68c2d1d8ffb1adbf30004ded', nm: 'Ragnar Lothbrok', isActive: true },
category: [{ id: '68c92a0426291fe3408a8141', nm: 'Technology', slug: 'technology', isActive: true }],
},
]);
}
} catch (err) {
console.error('Error seeding blogs:', err);
}
}

// Seed blog categories
async function seedBlogCategories() {
try {
const count = await BlogCategory.countDocuments();
if (count === 0) {
await BlogCategory.insertMany([
{
_id: new mongoose.Types.ObjectId('68c92a0426291fe3408a8141'),
nm: 'Technology',
slug: 'technology',
description: 'Articles about technology, programming, and software development',
isActive: true,
isDeleted: false,
createdBy: new mongoose.Types.ObjectId('68c2d1d8ffb1adbf30004ded'),
updatedBy: [new mongoose.Types.ObjectId('68c2d1d8ffb1adbf30004ded')],
},
{
_id: new mongoose.Types.ObjectId('68c92a0426291fe3408a8142'),
nm: 'Database',
slug: 'database',
description: 'Database design, optimization, and best practices',
isActive: true,
isDeleted: false,
createdBy: new mongoose.Types.ObjectId('68c2d1d8ffb1adbf30004ded'),
updatedBy: [new mongoose.Types.ObjectId('68c2d1d8ffb1adbf30004ded')],
},
{
_id: new mongoose.Types.ObjectId('68c92a0426291fe3408a8143'),
nm: 'Design',
slug: 'design',
description: 'UI/UX design, CSS, and frontend development',
isActive: true,
isDeleted: false,
createdBy: new mongoose.Types.ObjectId('68c2d1d8ffb1adbf30004ded'),
updatedBy: [new mongoose.Types.ObjectId('68c2d1d8ffb1adbf30004ded')],
},
]);
console.log('Blog categories seeded successfully');
}
} catch (err) {
console.error('Error seeding blog categories:', err);
}
}

const port = process.env.port || 3333;
const server = app.listen(port, () => {
const server = app.listen(port, async () => {
console.log('Listening at http://localhost:' + port);
await seedBlogCategories();
await seedBlogs();
});
server.on('error', console.error);
20 changes: 20 additions & 0 deletions apps/api/src/models/blog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Schema, model } from 'mongoose';
import mongoosePaginate from 'mongoose-paginate-v2';

const blogSchema = new Schema(
{
title: String,
name: String,
description: String,
slug: String,
coverImage: String,
isPublished: { type: Boolean, default: false },
isActive: { type: Boolean, default: true },
isDeleted: { type: Boolean, default: false },
},
{ strict: false, timestamps: true }
);

blogSchema.plugin(mongoosePaginate);
const Blog = model('blog', blogSchema);
export default Blog;
19 changes: 19 additions & 0 deletions apps/api/src/models/blogCategory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Schema, model } from 'mongoose';
import mongoosePaginate from 'mongoose-paginate-v2';

const blogCategorySchema = new Schema(
{
nm: String,
slug: String,
description: String,
isActive: { type: Boolean, default: true },
isDeleted: { type: Boolean, default: false },
createdBy: Schema.Types.ObjectId,
updatedBy: [Schema.Types.ObjectId],
},
{ timestamps: true }
);

blogCategorySchema.plugin(mongoosePaginate);
const BlogCategory = model('blogCategory', blogCategorySchema, 'blogCategory');
export default BlogCategory;
10 changes: 6 additions & 4 deletions apps/pagecreator/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import * as ReactDOM from 'react-dom/client';

import App from './app/app';

const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(<App />);
const rootElement = document.getElementById('root') as HTMLElement | null;

if (rootElement) {
const root = ReactDOM.createRoot(rootElement);
root.render(<App /> as unknown as Parameters<typeof root.render>[0]);
}
4 changes: 2 additions & 2 deletions libs/admin/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@knovator/pagecreator-admin",
"version":"1.2.6",
"version": "1.7.4",
"dependencies": {
"classnames": "^2.3.1",
"react-beautiful-dnd": "^13.1.0",
Expand Down Expand Up @@ -35,4 +35,4 @@
"index.js",
"index.cjs"
]
}
}
4 changes: 4 additions & 0 deletions libs/admin/src/lib/api/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ const apiList = {
url: `${prefix}/languages`,
method: 'GET',
}),
BLOG_CATEGORIES: ({ prefix }: API_INPUT_TYPE) => ({
url: `${prefix}/blog-categories`,
method: 'GET',
}),
// Image Upload API
IMAGE_UPLOAD: ({ prefix }: API_INPUT_TYPE) => ({
url: `${prefix}/upload`,
Expand Down
36 changes: 33 additions & 3 deletions libs/admin/src/lib/components/Page/Form/PageForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ import {
import { CONSTANTS } from '../../../constants/common';
import { useProviderState } from '../../../context/ProviderContext';

const PageForm = ({ formRef }: FormProps) => {
const PageForm = ({
formRef,
onFilterClick,
filterQuery,
onPrimaryButtonClick,
}: FormProps) => {
const { commonTranslations } = useProviderState();
const {
data,
Expand Down Expand Up @@ -109,6 +114,26 @@ const PageForm = ({ formRef }: FormProps) => {
if (destination) onChangeWidgetSequence(source.index, destination.index);
};

const handlePageSubmit = (formData: Record<string, unknown>) => {
const dataToSubmit =
typeof filterQuery !== 'undefined'
? { ...formData, filterQuery }
: formData;
const submitPayload =
Array.isArray(selectedWidgets) && selectedWidgets.length > 0
? {
...dataToSubmit,
widgets: selectedWidgets.map((item) => ({
_id: item.value,
label: item.label,
code: item.code,
})),
}
: dataToSubmit;
onPrimaryButtonClick?.(undefined, submitPayload);
return onPageFormSubmit(dataToSubmit);
};

// Schemas
const pageFormSchema: SchemaType[] = [
{
Expand Down Expand Up @@ -163,7 +188,7 @@ const PageForm = ({ formRef }: FormProps) => {
<div className="khb_form">
<SimpleForm
schema={pageFormSchema}
onSubmit={onPageFormSubmit}
onSubmit={handlePageSubmit}
ref={formRef}
isUpdating={formState === 'UPDATE'}
register={register}
Expand All @@ -186,7 +211,12 @@ const PageForm = ({ formRef }: FormProps) => {
}}
/> */}

<DNDItemsList onDragEnd={onDragEnd} items={selectedWidgets} />
<DNDItemsList
onDragEnd={onDragEnd}
onFilterClick={onFilterClick ? () => onFilterClick(data) : undefined}
items={selectedWidgets}
disableSettings={data?.canDel === false}
/>
</div>
);
};
Expand Down
Loading