Quellcode durchsuchen

full antd component on design

master
azri vor 2 Tagen
Ursprung
Commit
9fd2f6c5b0

+ 50
- 0
app/api/user/knowledgeService.ts Datei anzeigen

@@ -0,0 +1,50 @@
1
+import type { Knowledge } from "@/types/knowledge";
2
+import { dummyPersonaData } from "./personaService";
3
+
4
+const dummyKnowledgeData: Knowledge[] = [
5
+    {
6
+        id: 1,
7
+        uniq_slug: "refund_policy",
8
+        name: "Refund Policy",
9
+        personas: [
10
+            dummyPersonaData[0],
11
+            dummyPersonaData[1]
12
+        ]
13
+    },
14
+    {
15
+        id: 2,
16
+        uniq_slug: "pricing_sop",
17
+        name: "Pricing SOP",
18
+        personas: [
19
+            dummyPersonaData[0],
20
+            dummyPersonaData[2]
21
+        ]
22
+    },
23
+    {
24
+        id: 3,
25
+        uniq_slug: "response_templates",
26
+        name: "Response Templates",
27
+        personas: [
28
+            dummyPersonaData[1],
29
+            dummyPersonaData[2]
30
+        ]
31
+    },
32
+    {
33
+        id: 4,
34
+        uniq_slug: "customer_faq",
35
+        name: "Customer FAQ",
36
+        personas: [
37
+            dummyPersonaData[0],
38
+            dummyPersonaData[1],
39
+            dummyPersonaData[2]
40
+        ]
41
+    },
42
+];
43
+
44
+export const getAllKnowledge = async (): Promise<Knowledge[]> => {
45
+    return dummyKnowledgeData
46
+}
47
+
48
+export const getKnowledge = async (id: number): Promise<Knowledge> => {
49
+    return dummyKnowledgeData[id]
50
+}

+ 0
- 4
app/api/user/personaService.ts Datei anzeigen

@@ -53,13 +53,9 @@ export const dummyPersonaData: Persona[] = [
53 53
 ]
54 54
 
55 55
 export const getPersona = async (): Promise<Persona[]> => {
56
-
57 56
     return dummyPersonaData
58
-
59 57
 }
60 58
 
61 59
 export const getPersonaStyle = async (): Promise<PersonaStyle[]> => {
62
-
63 60
     return dummyPersonaStyle
64
-
65 61
 }

+ 29
- 0
app/globals.css Datei anzeigen

@@ -24,3 +24,32 @@ body {
24 24
   color: var(--foreground);
25 25
   font-family: Arial, Helvetica, sans-serif;
26 26
 }
27
+
28
+body, * {
29
+  scrollbar-width: thin;
30
+  scrollbar-color: rgba(100, 100, 100, 0.3) transparent;
31
+}
32
+
33
+.custom-center-menu > .ant-menu-item {
34
+  margin-left: auto !important;
35
+  margin-right: auto !important;
36
+}
37
+
38
+/* Target the whole page or a specific container */
39
+::-webkit-scrollbar {
40
+  width: 2px;        /* vertical scrollbar */
41
+  height: 4px;       /* horizontal scrollbar */
42
+}
43
+
44
+::-webkit-scrollbar-track {
45
+  background: transparent;
46
+}
47
+
48
+::-webkit-scrollbar-thumb {
49
+  background-color: rgba(100, 100, 100, 0.3);  /* light gray thumb */
50
+  border-radius: 10px;
51
+}
52
+
53
+::-webkit-scrollbar-thumb:hover {
54
+  background-color: rgba(100, 100, 100, 0.6);
55
+}

+ 12
- 3
app/layout.tsx Datei anzeigen

@@ -6,8 +6,17 @@ import type { ThemeConfig } from 'antd';
6 6
 
7 7
 import QueryProvider from '@/components/general/QueryProvider';
8 8
 
9
-const theme: ThemeConfig = {
10
-  // your theme config
9
+export const theme: ThemeConfig = {
10
+  token: {
11
+    colorPrimary: '#602FD0',
12
+    colorPrimary2: '#9E7DEA',
13
+    colorPrimary3: '#AD3B91',
14
+    colorSecondary: '#B3B3B3',
15
+    colorSuccess: '#35B37E',
16
+    colorWarning: '#FFAB00',
17
+    colorError: '#DB4336',
18
+    colorInfo: '#0165FF',
19
+  } as any,
11 20
 };
12 21
 
13 22
 export const metadata: Metadata = {
@@ -19,7 +28,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
19 28
   return (
20 29
     <html lang="en">
21 30
       <body className="bg-white flex justify-center relative">
22
-        <QueryProvider> {/* ✅ now inside client wrapper */}
31
+        <QueryProvider>
23 32
           <ConfigProvider theme={theme}>
24 33
             {children}
25 34
           </ConfigProvider>

+ 97
- 58
app/user/chat/[chatID]/page.tsx Datei anzeigen

@@ -1,91 +1,130 @@
1 1
 'use client';
2 2
 import { useState } from 'react';
3 3
 import type { Chat } from '@/types/chat';
4
+import { Flex, Layout, Row, Col, Input, Space, Upload, Button } from 'antd';
5
+import PageTitle from '@/components/ui/PageTitle';
6
+import Navigation from '@/components/layout/Navigation'
4 7
 import { useParams } from 'next/navigation'
5 8
 import { useQuery } from '@tanstack/react-query';
6 9
 import { getChat } from '@/app/api/user/chatService';
10
+import { PaperClipOutlined, SendOutlined } from '@ant-design/icons'
11
+
7 12
 
8 13
 const ChatPage: React.FC = () => {
9 14
 
10 15
     const params = useParams()
11 16
     const chatID = Number(params.chatID);
12 17
 
13
-    const {data:chat, error, isLoading} = useQuery<Chat | undefined>({
18
+    const { data: chat, error, isLoading } = useQuery<Chat | undefined>({
14 19
         queryKey: ["getChat"],
15 20
         queryFn: () => getChat(chatID)
16 21
     })
17 22
 
18 23
     const [enabled, setEnabled] = useState(false);
19 24
 
20
-    if(isLoading){
25
+    if (isLoading) {
21 26
         return <p>Loading...</p>
22 27
     }
23 28
 
24
-    if(error){
29
+    if (error) {
25 30
         return <p>Loading...</p>
26 31
     }
27 32
 
28 33
     return (
29
-        <div className="flex flex-col bg-gray-100 h-screen">
30
-            {/* Header */}
31
-            <div className="bg-white shadow p-4 flex flex-row">
34
+        <Layout className="!w-full !max-w-[430px] !mx-auto !relative !bg-white">
35
+            <Flex vertical className='!h-screen'>
36
+                <Row>
37
+                    <Col span={24}>
38
+                        <PageTitle position='center' backButton={true}>{chat?.name}</PageTitle>
39
+                        <div className="bg-white shadow p-4 flex flex-row border-b-2">
40
+                            <div>
41
+                                <img
42
+                                    src="/default-avatar.png"
43
+                                    alt="Profile"
44
+                                    className="w-15 h-15 rounded-full object-cover border"
45
+                                />
46
+                            </div>
47
+                            <div className='ps-2'>
48
+                                <p className="text-xl text-gray-500">{ }</p>
49
+                                <p className="text-md text-gray-500">{chat?.persona?.role}</p>
50
+                            </div>
51
+                            <div className='ps-2 flex flex-row gap-2 items-center ms-auto'>
52
+                                <span>Live</span>
53
+                                <button
54
+                                    onClick={() => setEnabled(!enabled)}
55
+                                    className={`w-12 h-6 flex items-center rounded-full p-1 transition-colors duration-300 ${enabled ? 'bg-blue-500' : 'bg-gray-300'
56
+                                        }`}
57
+                                >
58
+                                    <div
59
+                                        className={`bg-white w-4 h-4 rounded-full shadow-md transform transition-transform duration-300 ${enabled ? 'translate-x-6' : 'translate-x-0'
60
+                                            }`}
61
+                                    />
62
+                                </button>
63
+                                <span>Offline</span>
64
+
65
+                            </div>
66
+                        </div>
67
+                    </Col>
68
+                </Row>
69
+
70
+                <Row className='flex-1 overflow-y-scroll overflow-x-hidden'>
71
+                    <Col span={24}>
72
+                        <Flex vertical className="!bg-white">
73
+                            {/* Chat messages */}
74
+                            <div className="flex-1 overflow-y-auto px-4 py-6 space-y-4">
75
+                                {chat?.chatList.map((msg, index) => (
76
+                                    <div key={index}>
77
+                                        <p className={`mb-1 text-gray-500 ${msg.self ? 'text-left' : 'text-right'}`}>
78
+                                            {msg.self ? 'Visitor' : 'Ruccan Chat'}
79
+                                        </p>
80
+                                        <div className={`flex ${msg.self ? 'justify-start' : 'justify-end'}`}>
81
+                                            <div className={`max-w-xs whitespace-pre-wrap px-4 py-2 rounded-lg text-sm ${msg.self ? 'bg-white text-gray-800 shadow' : 'bg-blue-500 text-white'}`}>
82
+                                                {msg.text}
83
+                                                <div className="text-[10px] mt-1 text-right opacity-70">
84
+                                                    {msg.time}
85
+                                                </div>
86
+                                            </div>
87
+                                        </div>
88
+                                    </div>
89
+                                ))}
90
+                            </div>
91
+
92
+
93
+                        </Flex>
94
+                    </Col>
95
+                </Row>
96
+
32 97
                 <div>
33
-                    <img
34
-                        src="/default-avatar.png"
35
-                        alt="Profile"
36
-                        className="w-15 h-15 rounded-full object-cover border"
37
-                    />
38
-                </div>
39
-                <div className='ps-2'>
40
-                    <p className="text-xl text-gray-500">{}</p>
41
-                    <p className="text-md text-gray-500">{chat?.persona?.role}</p>
42
-                </div>
43
-                <div className='ps-2 flex flex-row gap-2 items-center ms-auto'>
44
-                    <span>Live</span>
45
-                    <button
46
-                        onClick={() => setEnabled(!enabled)}
47
-                        className={`w-12 h-6 flex items-center rounded-full p-1 transition-colors duration-300 ${enabled ? 'bg-blue-500' : 'bg-gray-300'
48
-                            }`}
49
-                    >
50
-                        <div
51
-                            className={`bg-white w-4 h-4 rounded-full shadow-md transform transition-transform duration-300 ${enabled ? 'translate-x-6' : 'translate-x-0'
52
-                                }`}
98
+                    <Flex vertical={false} className="!bg-gray-200 !p-4" align="center" justify="space-between">
99
+                        <Input.TextArea
100
+                            placeholder="Write a message"
101
+                            className='!bg-gray-200 !border-0 !text-black'
102
+                            autoSize={{ minRows: 1, maxRows: 4 }}
103
+                            style={{ color: 'black', fontSize: 20, resize: 'none', flex: 1 }}
53 104
                         />
54
-                    </button>
55
-                    <span>Offline</span>
105
+                        <Space size="middle" className="!ps-3">
106
+                            <Upload showUploadList={false}>
107
+                                <Button
108
+                                    icon={<PaperClipOutlined style={{ fontSize: 24 }} />}
109
+                                    type="text"
110
+                                    size="large"
111
+                                    style={{ height: 28, width: 28, padding: 0 }}
112
+                                />
113
+                            </Upload>
114
+                            <Button
115
+                                icon={<SendOutlined style={{ fontSize: 24 }} />}
116
+                                type="text"
117
+                                size="large"
118
+                                style={{ height: 28, width: 28, padding: 0 }}
119
+                            />
120
+                        </Space>
56 121
 
122
+                    </Flex>
123
+                    <Navigation />
57 124
                 </div>
58
-            </div>
59
-
60
-            {/* Chat messages */}
61
-            <div className="flex-1 overflow-y-auto px-4 py-6 space-y-4" style={{ maxHeight: "65vh" }}>
62
-                {chat?.chatList.map((msg, index) => (
63
-                    <div key={index}>
64
-                        <p className={`mb-1 text-gray-500 ${msg.self ? 'text-left' : 'text-right'}`}>
65
-                            {msg.self ? 'Visitor' : 'Ruccan Chat'}
66
-                        </p>
67
-                        <div className={`flex ${msg.self ? 'justify-start' : 'justify-end'}`}>
68
-                            <div className={`max-w-xs whitespace-pre-wrap px-4 py-2 rounded-lg text-sm ${msg.self ? 'bg-white text-gray-800 shadow' : 'bg-blue-500 text-white'}`}>
69
-                                {msg.text}
70
-                                <div className="text-[10px] mt-1 text-right opacity-70">
71
-                                    {msg.time}
72
-                                </div>
73
-                            </div>
74
-                        </div>
75
-                    </div>
76
-                ))}
77
-            </div>
78
-
79
-            {/* Input area */}
80
-            <div className="p-4 pb-0 bg-white border-t">
81
-                <input
82
-                    type="text"
83
-                    placeholder="Taip mesej..."
84
-                    className="w-full border rounded-full px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
85
-                />
86
-            </div>
87
-        </div>
88 125
 
126
+            </Flex>
127
+        </Layout>
89 128
     );
90 129
 };
91 130
 

+ 80
- 39
app/user/chat/page.tsx Datei anzeigen

@@ -1,64 +1,105 @@
1 1
 "use client";
2 2
 
3 3
 import React from 'react';
4
-import { Layout } from 'antd';
5
-import type { Chat } from '@/types/chat';
6
-import PageTitle from '@/components/ui/PageTitle';
4
+import { Flex, Typography, Layout } from 'antd';
7 5
 import { useRouter } from 'next/navigation';
8 6
 import { useQuery } from '@tanstack/react-query';
7
+
8
+import PageTitle from '@/components/ui/PageTitle';
9
+import type { Chat } from '@/types/chat';
9 10
 import { getAllChat } from '@/app/api/user/chatService';
10 11
 
11
-const Chat: React.FC = () => {
12
+const { Text } = Typography;
12 13
 
14
+const ChatPage: React.FC = () => {
13 15
     const router = useRouter();
14
-    
15
-    const {data:chatList, error, isLoading} = useQuery<Chat[] | undefined | null>({
16
-        queryKey:["getAllChat"],
17
-        queryFn: () => getAllChat()
18
-    })
16
+
17
+    // Fetch chat data using React Query
18
+    const {
19
+        data: chatList = [],
20
+        error,
21
+        isLoading,
22
+    } = useQuery<Chat[] | undefined | null>({
23
+        queryKey: ['getAllChat'],
24
+        queryFn: getAllChat,
25
+    });
26
+
27
+    // Show loading state while fetching data
28
+    if (isLoading) {
29
+        return (
30
+            <Layout>
31
+                <PageTitle>RUCCAN CHAT</PageTitle>
32
+                {/* You can drop in a Skeleton or Spinner here later */}
33
+            </Layout>
34
+        );
35
+    }
36
+
37
+    // Optional: handle unexpected empty/null data
38
+    if (!chatList || chatList.length === 0) {
39
+        return (
40
+            <Layout>
41
+                <PageTitle>RUCCAN CHAT</PageTitle>
42
+                <Flex vertical justify="center" align="center">
43
+                    <Text type="secondary">No chat data available.</Text>
44
+                </Flex>
45
+            </Layout>
46
+        );
47
+    }
19 48
 
20 49
     return (
21 50
         <Layout>
22
-            <PageTitle title='RUCCAN CHAT' />
23
-            <div className="flex flex-col h-screen w-full bg-white">
24
-                {/* Chat List */}
25
-                {chatList?.map((chat) => {
51
+            <PageTitle>RUCCAN CHAT</PageTitle>
26 52
 
27
-                    const lastChat = chat.chatList[chat.chatList.length - 1] 
53
+            {/* Chat List */}
54
+            <Flex vertical className="!w-full !bg-white overflow-y-auto">
55
+                {chatList.map((chat) => {
56
+                    // Get last message from chat
57
+                    const lastChat = chat.chatList?.[chat.chatList.length - 1];
28 58
 
29
-                    const unreadCount = chat.chatList.reduce((accum, chatItem) => {
30
-                        return chatItem.read ? accum : accum + 1;
31
-                    }, 0);
59
+                    // Count unread messages
60
+                    const unreadCount = chat.chatList?.reduce(
61
+                        (acc, item) => (!item.read ? acc + 1 : acc),
62
+                        0
63
+                    ) ?? 0;
32 64
 
33 65
                     return (
34
-                        <div
66
+                        <Flex
35 67
                             key={chat.id}
36
-                            className="px-4 py-3 border-b border-gray-100 hover:bg-gray-50 cursor-pointer flex flex-col gap-1"
37
-                            onClick={()=>{
38
-                                router.push(`/user/chat/${chat.id}`)
39
-                            }}
68
+                            vertical
69
+                            gap={4}
70
+                            className="!px-4 !py-3 !border-b !border-gray-100 hover:!bg-gray-50 !cursor-pointer"
71
+                            onClick={() => router.push(`/user/chat/${chat.id}`)}
40 72
                         >
41
-                            <div className="flex justify-between items-center">
42
-                                <span className="font-medium text-gray-900">{chat.name}</span>
43
-                                <span className="text-xs text-gray-500">{lastChat.time}</span>
44
-                            </div>
45
-                            <div className="flex justify-between items-center">
46
-                                <p className="text-sm text-gray-600 truncate max-w-[250px]">
47
-                                    {lastChat.text}
48
-                                </p>
49
-                                {(unreadCount > 0) && (
50
-                                    <span className="bg-[#72c4ff] text-white text-xs font-bold px-2 py-0.5 rounded-full">
73
+                            {/* Chat Name & Time */}
74
+                            <Flex justify="space-between" align="center">
75
+                                <Text className="!font-medium text-gray-900">{chat.name}</Text>
76
+                                <Text className="!text-xs text-gray-500">
77
+                                    {lastChat?.time ?? ''}
78
+                                </Text>
79
+                            </Flex>
80
+
81
+                            {/* Last Message & Unread Count */}
82
+                            <Flex justify="space-between" align="center">
83
+                                <Text
84
+                                    className="!text-sm text-gray-600 truncate max-w-[250px]"
85
+                                    title={lastChat?.text}
86
+                                >
87
+                                    {lastChat?.text ?? 'No messages yet.'}
88
+                                </Text>
89
+
90
+                                {/* Show unread count if > 0 */}
91
+                                {unreadCount > 0 && (
92
+                                    <Text className="!bg-[#72c4ff] !text-white !text-xs !font-bold px-2 py-0.5 !rounded-full">
51 93
                                         {unreadCount}
52
-                                    </span>
94
+                                    </Text>
53 95
                                 )}
54
-
55
-                            </div>
56
-                        </div>
57
-                    )
96
+                            </Flex>
97
+                        </Flex>
98
+                    );
58 99
                 })}
59
-            </div>
100
+            </Flex>
60 101
         </Layout>
61 102
     );
62 103
 };
63 104
 
64
-export default Chat;
105
+export default ChatPage;

+ 161
- 140
app/user/knowledge/create/page.tsx Datei anzeigen

@@ -2,13 +2,16 @@
2 2
 
3 3
 import { useState, useEffect } from 'react'
4 4
 import PageTitle from '@/components/ui/PageTitle';
5
-import { Upload, Select, Switch, Form, Button } from 'antd'
5
+import { Upload, Select, Switch, Form, Button, Tag, Layout, Typography, Input, Row, Col, Flex } from 'antd'
6 6
 import { UploadOutlined } from '@ant-design/icons';
7 7
 import InputList from '@/components/ui/InputList';
8 8
 import type { SelectProps } from 'antd';
9 9
 import TextArea from 'antd/es/input/TextArea';
10 10
 import { useQuery } from '@tanstack/react-query';
11 11
 import { getPersona } from '@/app/api/user/personaService';
12
+import AltLayout from '@/components/layout/AltLayout';
13
+
14
+const { Title, Text } = Typography;
12 15
 
13 16
 const CreatePersona: React.FC = () => {
14 17
 
@@ -47,145 +50,163 @@ const CreatePersona: React.FC = () => {
47 50
     }, [personas])
48 51
 
49 52
     return (
50
-        <div className="flex flex-col bg-white">
51
-            <PageTitle title='ADD KNOWLEDGE SOURCE' />
52
-            <div className='px-4 py-6'>
53
-                <Form layout="vertical">
54
-                    <Form.Item label={<p className='font-bold'>Select Persona</p>}>
55
-                        <Select
56
-                            allowClear
57
-                            style={{ width: '100%' }}
58
-                            placeholder="Select from list ..."
59
-                            options={options}
60
-                        />
61
-                    </Form.Item>
62
-
63
-                    <Form.Item label={<p className='font-bold'>Description</p>}>
64
-                        <TextArea
65
-                            placeholder='Add a short description about what Persona does'
66
-                            style={{ padding: 10 }}
67
-                            rows={2}
68
-                            value={name}
69
-                            onChange={(e) => setName(e.target.value)}
70
-                        />
71
-                    </Form.Item>
72
-
73
-                    <Form.Item label={<p className='font-bold'>Instruction</p>}>
74
-                        <TextArea
75
-                            placeholder='What does this Persona do?, How does it behave?, What should it avoid doing? '
76
-                            style={{ padding: 10 }}
77
-                            rows={4}
78
-                            value={name}
79
-                            onChange={(e) => setName(e.target.value)}
80
-                        />
81
-                    </Form.Item>
82
-
83
-                    <div className={`${isPremium ? 'visible' : 'hidden'}`}>
84
-                        <Form.Item
85
-                            label={
86
-                                <p className='font-bold'>
87
-                                    Capabilities
88
-                                    <span className='text-xs font-semibold px-2 py-1 bg-teal-200 rounded-full mx-2'>Premium</span>
89
-                                </p>
90
-                            }
91
-                        >
92
-                            <div className='flex flex-col gap-4'>
93
-
94
-                                {/* Web Search */}
95
-                                <div style={{maxHeight:"65px"}}>
96
-                                    <div className='flex items-center gap-2'>
97
-                                        <Switch
98
-                                            checked={enabled.isWebSearch}
99
-                                            onChange={(checked) =>
100
-                                                setEnabled({ ...enabled, isWebSearch: checked })
101
-                                            }
102
-                                        />
103
-                                        <span className='font-bold'>Web Search</span>
104
-                                    </div>
105
-                                    {enabled.isWebSearch && <InputList />}
106
-                                </div>
107
-
108
-                                {/* Commerce & Shop */}
109
-                                <div>
110
-                                    <div className='flex items-center gap-2'>
111
-                                        <Switch
112
-                                            checked={enabled.isEcommerceShop}
113
-                                            onChange={(checked) =>
114
-                                                setEnabled({ ...enabled, isEcommerceShop: checked })
115
-                                            }
116
-                                        />
117
-                                        <span className='font-bold'>Commerce & Shop</span>
118
-                                    </div>
119
-                                    {enabled.isEcommerceShop && (
120
-                                        <Upload {...uploadProps} className='!mt-2 block'>
121
-                                            <Button icon={<UploadOutlined />}>Upload Shop Catalog</Button>
122
-                                        </Upload>
123
-                                    )}
124
-                                </div>
125
-
126
-                                {/* Canvas */}
127
-                                <div>
128
-                                    <div className='flex items-center gap-2'>
129
-                                        <Switch
130
-                                            checked={enabled.isCanvas}
131
-                                            onChange={(checked) =>
132
-                                                setEnabled({ ...enabled, isCanvas: checked })
133
-                                            }
134
-                                        />
135
-                                        <span className='font-bold'>Canvas</span>
136
-                                    </div>
137
-                                    {enabled.isCanvas && (
138
-                                        <Upload {...uploadProps} className='!mt-2 block'>
139
-                                            <Button icon={<UploadOutlined />}>Upload Canvas File</Button>
140
-                                        </Upload>
141
-                                    )}
142
-                                </div>
143
-
144
-                                {/* DALL·E Image Generation */}
145
-                                <div>
146
-                                    <div className='flex items-center gap-2'>
147
-                                        <Switch
148
-                                            checked={enabled.isDalleImageGeneration}
149
-                                            onChange={(checked) =>
150
-                                                setEnabled({ ...enabled, isDalleImageGeneration: checked })
151
-                                            }
152
-                                        />
153
-                                        <span className='font-bold'>DALL-E Image Generation</span>
154
-                                    </div>
155
-                                    {enabled.isDalleImageGeneration && (
156
-                                        <Upload {...uploadProps} className='!mt-2 block'>
157
-                                            <Button icon={<UploadOutlined />}>Upload Prompt CSV</Button>
158
-                                        </Upload>
159
-                                    )}
160
-                                </div>
161
-
162
-                                {/* Code Interpreter */}
163
-                                <div>
164
-                                    <div className='flex items-center gap-2'>
165
-                                        <Switch
166
-                                            checked={enabled.isCodeInterpreter}
167
-                                            onChange={(checked) =>
168
-                                                setEnabled({ ...enabled, isCodeInterpreter: checked })
169
-                                            }
170
-                                        />
171
-                                        <span className='font-bold'>Code Interpreter & Data Analysis</span>
172
-                                    </div>
173
-                                    {enabled.isCodeInterpreter && (
174
-                                        <Upload {...uploadProps} className='!mt-2 block'>
175
-                                            <Button icon={<UploadOutlined />}>Upload Data File</Button>
176
-                                        </Upload>
177
-                                    )}
178
-                                </div>
179
-                            </div>
180
-                        </Form.Item>
181
-                    </div>
182
-
183
-                    <div className='text-center'>
184
-                        <Button type="primary" className='!w-fit !py-3 !text-xs !font-bold !px-10'>SUBMIT</Button>
185
-                    </div>
186
-                </Form>
187
-            </div>
188
-        </div>
53
+        <AltLayout header={<PageTitle backButton={true}>ADD KNOWLEDGE SOURCE</PageTitle>}>
54
+            <Row>
55
+                <Col span={24} className='!px-4'>
56
+                    <Form layout="vertical">
57
+
58
+                        <Row>
59
+                            <Col span={24}>
60
+
61
+                                <Form.Item label={<p className='font-bold'>Name</p>}>
62
+                                    <Input />
63
+                                </Form.Item>
64
+
65
+                                <Form.Item label={<p className='font-bold'>Select Persona</p>}>
66
+                                    <Select
67
+                                        allowClear
68
+                                        style={{ width: '100%' }}
69
+                                        placeholder="Select from list ..."
70
+                                        options={options}
71
+                                    />
72
+                                </Form.Item>
73
+
74
+                                <Form.Item label={<p className='font-bold'>Description</p>}>
75
+                                    <TextArea
76
+                                        placeholder='Add a short description about what Persona does'
77
+                                        style={{ padding: 10 }}
78
+                                        rows={2}
79
+                                        value={name}
80
+                                        onChange={(e) => setName(e.target.value)}
81
+                                    />
82
+                                </Form.Item>
83
+
84
+                                <Form.Item label={<p className='font-bold'>Instruction</p>}>
85
+                                    <TextArea
86
+                                        placeholder='What does this Persona do?, How does it behave?, What should it avoid doing? '
87
+                                        style={{ padding: 10 }}
88
+                                        rows={4}
89
+                                        value={name}
90
+                                        onChange={(e) => setName(e.target.value)}
91
+                                    />
92
+                                </Form.Item>
93
+
94
+                            </Col>
95
+
96
+                            <Col span={24} className={`${isPremium ? '!visible' : '!hidden'}`}>
97
+                                <Form.Item
98
+                                    label={
99
+                                        <Title className='font-bold !text-sm'>
100
+                                            Capabilities
101
+                                            <Tag color="blue" className="!ms-2 font-semibold text-xs px-2 py-[2px] !rounded-full">
102
+                                                Premium
103
+                                            </Tag>
104
+                                        </Title>
105
+                                    }
106
+                                >
107
+                                    <Row gutter={[5, 5]}>
108
+
109
+                                        {/* Web Search */}
110
+                                        <Col span={24}>
111
+                                            <Flex gap={8}>
112
+                                                <Switch
113
+                                                    checked={enabled.isWebSearch}
114
+                                                    onChange={(checked) =>
115
+                                                        setEnabled({ ...enabled, isWebSearch: checked })
116
+                                                    }
117
+                                                />
118
+                                                <Text className='font-bold'>Web Search</Text>
119
+                                            </Flex>
120
+                                            {enabled.isWebSearch && <InputList />}
121
+                                        </Col>
122
+
123
+                                        {/* Commerce & Shop */}
124
+                                        <Col span={24}>
125
+                                            <Flex gap={8}>
126
+                                                <Switch
127
+                                                    checked={enabled.isEcommerceShop}
128
+                                                    onChange={(checked) =>
129
+                                                        setEnabled({ ...enabled, isEcommerceShop: checked })
130
+                                                    }
131
+                                                />
132
+                                                <Text className='font-bold'>Commerce & Shop</Text>
133
+                                            </Flex>
134
+                                            {enabled.isEcommerceShop && (
135
+                                                <Upload {...uploadProps} className='!mt-2 block'>
136
+                                                    <Button icon={<UploadOutlined />}>Upload Shop Catalog</Button>
137
+                                                </Upload>
138
+                                            )}
139
+                                        </Col>
140
+
141
+                                        {/* Canvas */}
142
+                                        <Col span={24}>
143
+                                            <Flex gap={8}>
144
+                                                <Switch
145
+                                                    checked={enabled.isCanvas}
146
+                                                    onChange={(checked) =>
147
+                                                        setEnabled({ ...enabled, isCanvas: checked })
148
+                                                    }
149
+                                                />
150
+                                                <Text className='font-bold'>Canvas</Text>
151
+                                            </Flex>
152
+                                            {enabled.isCanvas && (
153
+                                                <Upload {...uploadProps} className='!mt-2 block'>
154
+                                                    <Button icon={<UploadOutlined />}>Upload Canvas File</Button>
155
+                                                </Upload>
156
+                                            )}
157
+                                        </Col>
158
+
159
+                                        {/* DALL·E Image Generation */}
160
+                                        <Col span={24}>
161
+                                            <Flex gap={8}>
162
+                                                <Switch
163
+                                                    checked={enabled.isDalleImageGeneration}
164
+                                                    onChange={(checked) =>
165
+                                                        setEnabled({ ...enabled, isDalleImageGeneration: checked })
166
+                                                    }
167
+                                                />
168
+                                                <Text className='font-bold'>DALL-E Image Generation</Text>
169
+                                            </Flex>
170
+                                            {enabled.isDalleImageGeneration && (
171
+                                                <Upload {...uploadProps} className='!mt-2 block'>
172
+                                                    <Button icon={<UploadOutlined />}>Upload Prompt CSV</Button>
173
+                                                </Upload>
174
+                                            )}
175
+                                        </Col>
176
+
177
+                                        {/* Code Interpreter */}
178
+                                        <Col span={24}>
179
+                                            <Flex gap={8}>
180
+                                                <Switch
181
+                                                    checked={enabled.isCodeInterpreter}
182
+                                                    onChange={(checked) =>
183
+                                                        setEnabled({ ...enabled, isCodeInterpreter: checked })
184
+                                                    }
185
+                                                />
186
+                                                <Text className='font-bold'>Code Interpreter & Data Analysis</Text>
187
+                                            </Flex>
188
+                                            {enabled.isCodeInterpreter && (
189
+                                                <Upload {...uploadProps} className='!mt-2 block'>
190
+                                                    <Button icon={<UploadOutlined />}>Upload Data File</Button>
191
+                                                </Upload>
192
+                                            )}
193
+                                        </Col>
194
+                                    </Row>
195
+                                </Form.Item>
196
+                            </Col>
197
+
198
+                            <Col span={24} className='!text-center'>
199
+                                <Flex justify='center'>
200
+                                    <Button type="primary" className='!w-fit !py-3 !text-xs !font-bold !px-10'>SUBMIT</Button>
201
+                                </Flex>
202
+                            </Col>
203
+                        </Row>
204
+
205
+
206
+                    </Form>
207
+                </Col>
208
+            </Row>
209
+        </AltLayout>
189 210
     )
190 211
 }
191 212
 

+ 126
- 84
app/user/knowledge/page.tsx Datei anzeigen

@@ -8,112 +8,154 @@ import {
8 8
     SortAscendingOutlined,
9 9
     PlusCircleFilled
10 10
 } from '@ant-design/icons';
11
+import type { Knowledge } from '@/types/knowledge';
12
+import { useRouter } from 'next/navigation';
13
+import { useQuery } from '@tanstack/react-query';
14
+import PrimaryButton from '@/components/ui/PrimaryButton';
15
+import { Flex, Row, Col, Tag, Button, Table, Layout, Image, Typography } from 'antd';
16
+import type { ColumnsType } from 'antd/es/table';
17
+import { getAllKnowledge } from '@/app/api/user/knowledgeService';
11 18
 
12
-type SourcePersonaItem = {
13
-    id: number;
14
-    persona_name: string;
15
-    knowledge_source: string[];
16
-}
19
+const { Text, Title } = Typography;
17 20
 
18
-const sourcePersonas: SourcePersonaItem[] = [
21
+const columns: ColumnsType<Knowledge> = [
19 22
     {
20
-        id: 1,
21
-        persona_name: "Bella",
22
-        knowledge_source: ["Refund Policy", "Pricing SOP"],
23
+        title: (
24
+            <Text className="flex items-center gap-1">
25
+                Knowledge Source <SortAscendingOutlined className="text-xs text-gray-400" />
26
+            </Text>
27
+        ),
28
+        dataIndex: 'name',
29
+        key: 'name'
23 30
     },
24 31
     {
25
-        id: 2,
26
-        persona_name: "Rafiq",
27
-        knowledge_source: ["Refund Policy", "Pricing SOP"],
32
+        title: (
33
+            <Text className="flex items-center gap-1">
34
+                AI Persona <SortAscendingOutlined className="text-xs text-gray-400" />
35
+            </Text>
36
+        ),
37
+        dataIndex: 'personas',
38
+        key: 'personas',
39
+        render: (_: any, record: Knowledge) => {
40
+            if (!record.personas?.length) return '-';
41
+
42
+            return (
43
+                <Flex wrap>
44
+                    {record.personas.map((persona) => (
45
+                        <Tag key={persona.id} color="blue">
46
+                            {persona.name}
47
+                        </Tag>
48
+                    ))}
49
+                </Flex>
50
+            );
51
+        },
28 52
     },
29 53
     {
30
-        id: 3,
31
-        persona_name: "Lina",
32
-        knowledge_source: ["Response Templates", "Customer FAQ"],
33
-    }
34
-];
54
+        title: 'Action',
55
+        key: 'action',
56
+        align: 'center' as const,
57
+        render: (_: any, record: Knowledge) => (
58
+            <Flex justify="center">
59
+                <Button type="text" icon={<EditOutlined />} />
60
+                <Button type="text" icon={<EyeOutlined />} />
61
+            </Flex>
62
+        ),
63
+    },
64
+]
35 65
 
36 66
 const Knowledge: React.FC = () => {
67
+
68
+    const { data: knowledges, error, isLoading } = useQuery<Knowledge[]>({
69
+        queryKey: ["getAllKnowledge"],
70
+        queryFn: () => getAllKnowledge()
71
+    })
72
+
73
+    if (isLoading) {
74
+        return <p>Loading...</p>
75
+    }
76
+
77
+    if (!knowledges) {
78
+        return <p></p>
79
+    }
80
+
37 81
     return (
38 82
         <main className="flex flex-col bg-white h-full">
39
-            <PageTitle title="KNOWLEDGE SOURCES" />
40
-            {sourcePersonas.length > 0 ? <KnowledgeList /> : <EmptyKnowledgeIntro />}
83
+            <PageTitle>KNOWLEDGE SOURCES</PageTitle>
84
+            {knowledges.length > 0 ? <KnowledgeList knowledgeData={knowledges} /> : <EmptyKnowledgeIntro />}
41 85
         </main>
42 86
     );
43 87
 };
44 88
 
45 89
 const EmptyKnowledgeIntro: React.FC = () => {
90
+
91
+    const router = useRouter();
92
+
46 93
     return (
47
-        <div className="flex flex-col px-6 flex-1 justify-center items-center">
48
-            <div
49
-                className="w-full max-w-md text-center border-2 border-cyan-500 rounded-lg px-6 py-10 shadow-lg"
50
-                style={{ boxShadow: '0 10px 100px 30px rgba(59, 130, 246, 0.3)' }}
51
-            >
52
-                <p className="text-lg font-semibold mb-2 text-gray-800">NO SOURCE ADDED YET</p>
53
-                <p className="text-sm text-gray-600 mb-4">Get started by adding your first knowledge source</p>
54
-
55
-                <img
56
-                    src="/ruccan_logo.png"
57
-                    alt="Ruccan Logo"
58
-                    className="mx-auto mb-6"
59
-                    width={150}
60
-                    height={150}
61
-                />
62
-
63
-                <button className="w-fit rounded text-xs px-4 py-2 bg-[#369cc1] hover:bg-[#82c7d7] cursor-pointer text-white font-bold shadow transition-all duration-200">
64
-                    ADD FIRST SOURCE
65
-                </button>
66
-            </div>
67
-        </div>
94
+        <Flex vertical flex={1} justify="center" align="center">
95
+            <Row justify="center" style={{ width: '100%', padding: '0 24px' }}>
96
+                <Col xs={24} sm={20} md={16} lg={12} xl={8}>
97
+                    <Flex
98
+                        vertical
99
+                        align="center"
100
+                        className="!text-center"
101
+                        style={{
102
+                            border: '2px solid #06b6d4', // cyan-500
103
+                            borderRadius: 12,
104
+                            padding: '40px 24px',
105
+                            boxShadow: '0 10px 100px 30px rgba(59, 130, 246, 0.3)',
106
+                            background: '#fff',
107
+                        }}
108
+                    >
109
+                        <Title level={5} style={{ color: '#1f2937', marginBottom: 8 }}>
110
+                            NO SOURCE ADDED YET
111
+                        </Title>
112
+
113
+                        <Text type="secondary" style={{ marginBottom: 24 }}>
114
+                            Get started by adding your first knowledge source
115
+                        </Text>
116
+
117
+                        <Image
118
+                            src="/ruccan_logo2.png"
119
+                            alt="Ruccan Logo"
120
+                            width={150}
121
+                            preview={false}
122
+                            style={{ marginBottom: 24 }}
123
+                        />
124
+
125
+                        <PrimaryButton onClick={()=>{router.push("/user/knowledge/create")}}>
126
+                            ADD FIRST SOURCE
127
+                        </PrimaryButton>
128
+                    </Flex>
129
+                </Col>
130
+            </Row>
131
+        </Flex>
68 132
     );
69 133
 };
70 134
 
71
-const KnowledgeList: React.FC = () => {
135
+interface KnowledgeListProps {
136
+    knowledgeData: Knowledge[] | undefined;
137
+}
138
+
139
+const KnowledgeList: React.FC<KnowledgeListProps> = ({ knowledgeData }) => {
140
+
141
+    const router = useRouter();
142
+
72 143
     return (
73
-        <div className="overflow-x-auto">
74
-            <div className="flex px-2 py-4">
75
-                <button className="w-fit rounded text-xs px-4 py-2 bg-[#369cc1] hover:bg-[#82c7d7] cursor-pointer text-white font-bold shadow transition-all duration-200 ms-auto">
144
+        <Layout className="!overflow-x-auto">
145
+            <Flex className='!px-2 !py-4 !justify-end'>
146
+                <PrimaryButton onClick={() => { router.push('knowledge/create') }}>
76 147
                     <PlusCircleFilled /> Add Source
77
-                </button>
78
-            </div>
79
-            <table className="w-full text-sm text-left text-gray-700 border-collapse">
80
-                <thead>
81
-                    <tr className="text-black">
82
-                        <th className="px-4 py-3 font-medium border border-gray-200">
83
-                            <div className="flex items-center gap-1 text-xs min-w-[80px]">
84
-                                AI persona <SortAscendingOutlined className="text-xs text-gray-400" />
85
-                            </div>
86
-                        </th>
87
-                        <th className="px-4 py-3 font-medium border border-gray-200">
88
-                            <div className="flex items-center gap-1 text-xs">
89
-                                Knowledge Sources <SortAscendingOutlined className="text-xs text-gray-400" />
90
-                            </div>
91
-                        </th>
92
-                        <th className="px-4 py-3 font-medium text-center border border-gray-200">Action</th>
93
-                    </tr>
94
-                </thead>
95
-                <tbody>
96
-                    {sourcePersonas.map((item) => (
97
-                        <tr key={item.id} className="border-t border-gray-200 hover:bg-gray-50">
98
-                            <td className="px-4 py-3">{item.persona_name}</td>
99
-                            <td className="px-4 py-3">
100
-                                {item.knowledge_source.join(', ')}
101
-                            </td>
102
-                            <td className="px-4 py-3 text-center">
103
-                                <div className="flex justify-center items-center gap-3 text-gray-600">
104
-                                    <button className="hover:text-blue-500" aria-label="Edit" title="Edit">
105
-                                        <EditOutlined />
106
-                                    </button>
107
-                                    <button className="hover:text-green-600" aria-label="View" title="View">
108
-                                        <EyeOutlined />
109
-                                    </button>
110
-                                </div>
111
-                            </td>
112
-                        </tr>
113
-                    ))}
114
-                </tbody>
115
-            </table>
116
-        </div>
148
+                </PrimaryButton>
149
+            </Flex>
150
+            <Table
151
+                className="[&_.ant-table-cell]:!p-2"
152
+                rowKey="id"
153
+                columns={columns}
154
+                dataSource={knowledgeData}
155
+                pagination={false}
156
+                bordered
157
+            />
158
+        </Layout>
117 159
     );
118 160
 };
119 161
 

+ 36
- 45
app/user/layout.tsx Datei anzeigen

@@ -1,58 +1,49 @@
1 1
 'use client'
2 2
 
3
-import { Layout, Menu } from 'antd'
4
-import {
5
-  HomeOutlined,
6
-  MessageOutlined,
7
-  UserOutlined,
8
-} from '@ant-design/icons'
3
+import React from 'react'
4
+import { Layout, Flex, Row, Col } from 'antd'
9 5
 import Header from '@/components/layout/Header'
10
-import { useRouter, usePathname } from 'next/navigation'
6
+import Navigation from '@/components/layout/Navigation'
7
+import { usePathname } from 'next/navigation'
11 8
 
12
-const { Content } = Layout
13 9
 
14 10
 const AppLayout = ({ children }: { children: React.ReactNode }) => {
15
-  const router = useRouter()
11
+
16 12
   const pathname = usePathname()
17 13
 
18
-  const navItems = [
19
-    {
20
-      key: '/user/dashboard',
21
-      icon: <HomeOutlined />,
22
-      label: 'Home',
23
-    },
24
-    {
25
-      key: '/user/chatboard',
26
-      icon: <MessageOutlined />,
27
-      label: 'Chat',
28
-    },
29
-    {
30
-      key: '/user/profile',
31
-      icon: <UserOutlined />,
32
-      label: 'Profile',
33
-    },
34
-  ]
14
+  const skipLayoutPrefixes = [
15
+    '/user/chat/',
16
+    '/user/persona/create',
17
+    '/user/knowledge/create'
18
+  ];
19
+
20
+  const shouldSkipLayout = skipLayoutPrefixes.some(prefix =>
21
+    pathname.startsWith(prefix)
22
+  );
23
+
24
+  if (shouldSkipLayout) {
25
+    return <>{children}</>;
26
+  }
35 27
 
36 28
   return (
37
-    <Layout className="w-full max-w-[430px] min-h-screen mx-auto shadow-xl relative">
38
-      <Header />
39
-
40
-      <Content className="flex-1 overflow-auto bg-white pb-20" style={{minHeight:"93vh"}}>
41
-        {children}
42
-      </Content>
43
-
44
-      {/* Bottom Nav */}
45
-      <div className="fixed bottom-0 w-full max-w-[430px] mx-auto left-0 right-0 border-t border-gray-200 shadow-inner z-50 bg-white">
46
-        <Menu
47
-          mode="horizontal"
48
-          selectedKeys={[pathname]}
49
-          onClick={({ key }) => router.push(key)}
50
-          items={navItems}
51
-          className="flex justify-around"
52
-        />
53
-      </div>
29
+    <Layout className="!w-full !max-w-[430px] !mx-auto !relative !bg-white">
30
+      <Flex vertical className='!h-screen'>
31
+        <Row>
32
+          <Col span={24}>
33
+            <Header />
34
+          </Col>
35
+        </Row>
36
+
37
+        <Row className='flex-1 overflow-y-scroll overflow-x-hidden'>
38
+          <Col span={24}>{children}</Col>
39
+        </Row>
40
+
41
+        <div>
42
+          <Navigation />
43
+        </div>
44
+      </Flex>
54 45
     </Layout>
55
-  )
46
+  );
56 47
 }
57 48
 
58
-export default AppLayout
49
+export default AppLayout;

+ 80
- 62
app/user/page.tsx Datei anzeigen

@@ -1,73 +1,91 @@
1 1
 "use client";
2 2
 
3
-import React from 'react'
4
-import { Flex, Typography, Divider, Layout, Button } from 'antd';
3
+import React from 'react';
4
+import { Flex, Typography, Divider, Layout, Button, Row, Col, Image } from 'antd';
5 5
 import { RightOutlined, PhoneOutlined } from '@ant-design/icons';
6 6
 import SectionTitle from '@/components/ui/SectionTitle';
7 7
 import PageTitle from '@/components/ui/PageTitle';
8
-
9 8
 import LoadingMeter from '@/components/ui/LoadingMeter';
9
+import PrimaryButton from '@/components/ui/PrimaryButton';
10 10
 
11 11
 const { Title, Text } = Typography;
12 12
 
13
-/* Main Dashboard */
14 13
 const page = () => {
15
-
16 14
     return (
17
-        <section className="space-y-4">
18
-
19
-            <PageTitle title='DASHBOARD' />
20
-
21
-            <div className='px-4'>
22
-                <SectionTitle title='Plan Details' />
23
-
24
-                <Layout className='border-2 rounded border-cyan-500 px-4 py-2'>
25
-                    <div>
26
-                        <Text strong>Plan: </Text>
27
-                        <Text className='!text-blue-500'>Free Plan</Text>
28
-                    </div>
29
-
30
-                    <Text className='!text-xs mb-2'>
31
-                        This product is still in beta testing. There might be some issue, use at your own risk.
32
-                    </Text>
33
-
34
-                    <div className='mb-2'>
35
-                        <PhoneOutlined className='me-1' style={{ color: "red" }} />
36
-                        <Text strong className='!text-xs'>Contact: </Text>
37
-                        <Text className='!text-blue-500 !text-xs'>Aliff Akmal Bahri</Text>
38
-                    </div>
39
-
40
-                    <div className='!mb-2'>
41
-                        <Button className='!w-fit !rounded-full !text-xs !bg-[#b64fb1] !text-white'>Upgrade Plan <RightOutlined /> </Button>
42
-                    </div>
43
-
44
-                    <div className='border-2 rounded border-cyan-500 !bg-cyan-500 px-4 py-2 flex flex-row flex-wrap'>
45
-                        <div className='text-white'>
46
-                            <p className='text-xs font-bold mb-2'>Ruccan AI Labs </p>
47
-                            <p className='text-xs'>Explore our latest AI experiments </p>
48
-                            <p className='text-xs text-[#ebda5a]'>New tools added regularly!</p>
49
-                        </div>
50
-                        <div className='mx-auto text-center'>
51
-                            <img src={"/flask.png"} width="75" className='mb-2 mx-auto'/>
52
-                            <button className='w-fit rounded-lg text-xs px-4 py-2 bg-[#369cc1] hover:bg-[#436d86] text-white shadow border-0'>
53
-                                <span className='me-1'>Upgrade Plan</span> <RightOutlined />
54
-                            </button>
55
-                        </div>
56
-                    </div>
57
-
58
-                </Layout>
59
-
60
-                <SectionTitle title='Usage Limits' />
61
-
62
-                <Layout className='border-2 rounded border-cyan-500 px-4 py-2 bg-white'>
63
-                    <LoadingMeter title='AI Persona' progress={3} total={3} />
64
-                    <LoadingMeter title='Monthly Messages' progress={12} total={100} />
65
-                    <LoadingMeter title='Knowledge Base Scans' progress={1} total={10} />
66
-                </Layout>
67
-            </div>
68
-
69
-        </section>
70
-    )
71
-}
72
-
73
-export default page
15
+        <Layout className="!space-y-4 !bg-white">
16
+            <PageTitle title="DASHBOARD" />
17
+
18
+            {/* Plan Details */}
19
+            <Row>
20
+                <Col span={24}>
21
+                    <SectionTitle title="Plan Details" />
22
+                </Col>
23
+
24
+                <Col span={24} className="px-4">
25
+                    <Row className="border-2 rounded border-cyan-500 px-4 py-2" >
26
+                        <Col span={24}>
27
+                            <Text strong>Plan: </Text>
28
+                            <Text type="success">Free Plan</Text>
29
+                            <Text className="!text-xs block mb-1">
30
+                                This product is still in beta testing. There might be some issues. Use at your own risk.
31
+                            </Text>
32
+                        </Col>
33
+
34
+                        <Col span={24} className='mb-2'>
35
+                            <PhoneOutlined className="me-1" style={{ color: 'red' }} />
36
+                            <Text strong className="!text-xs">Contact: </Text>
37
+                            <Text className="!text-blue-500 !text-xs">Aliff Akmal Bahri</Text>
38
+                        </Col>
39
+
40
+                        <Col span={24} className='mb-2'>
41
+                            <PrimaryButton onClick={()=>{}}>
42
+                                Upgrade Button <RightOutlined />
43
+                            </PrimaryButton>
44
+                        </Col>
45
+
46
+                        {/* Ruccan AI Labs Section */}
47
+                        <Col span={24} className="border-2 rounded border-cyan-500 !bg-cyan-600 !px-2 py-2 !mb-4">
48
+                            <Row>
49
+                                <Col span={12}>
50
+                                    <Text strong className="!text-xs !text-white block mb-2">Ruccan AI Labs</Text>
51
+                                    <Text className="!text-xs !text-white block mb-2">Explore our latest AI experiments</Text>
52
+                                    <Text className="!text-xs block !text-[#f7ea8e]">New tools added regularly!</Text>
53
+                                </Col>
54
+
55
+                                <Col span={12} className="text-center mt-2 flex flex-col">
56
+                                    <Image
57
+                                        src="/flask.png"
58
+                                        width={70}
59
+                                        preview={false}
60
+                                        className="mb-2 mx-auto"
61
+                                        alt="Ruccan AI Labs"
62
+                                    />
63
+                                    <PrimaryButton outline={true} onClick={()=>{alert("Hello")}}>
64
+                                        Explore Lab <RightOutlined />
65
+                                    </PrimaryButton>
66
+                                </Col>
67
+                            </Row>
68
+                        </Col>
69
+                    </Row>
70
+                </Col>
71
+            </Row>
72
+
73
+            {/* Usage Limits */}
74
+            <Row className='mb-5'>
75
+                <Col span={24}>
76
+                    <SectionTitle title="Usage Limits" />
77
+                </Col>
78
+
79
+                <Col span={24} className="px-4">
80
+                    <Layout className="border-2 rounded border-cyan-500 px-4 py-2 bg-white">
81
+                        <LoadingMeter title="AI Persona" progress={3} total={3} />
82
+                        <LoadingMeter title="Monthly Messages" progress={12} total={100} />
83
+                        <LoadingMeter title="Knowledge Base Scans" progress={1} total={10} />
84
+                    </Layout>
85
+                </Col>
86
+            </Row>
87
+        </Layout>
88
+    );
89
+};
90
+
91
+export default page;

+ 169
- 123
app/user/persona/create/page.tsx Datei anzeigen

@@ -2,19 +2,21 @@
2 2
 
3 3
 import { useState, useEffect } from 'react'
4 4
 import PageTitle from '@/components/ui/PageTitle';
5
-import { Input, Typography, Select, Switch, Upload, Form, Button } from 'antd'
5
+import { Input, Typography, Select, Switch, Upload, Form, Button, Tag, Col, Row, Flex } from 'antd'
6
+import AltLayout from '@/components/layout/AltLayout';
6 7
 import { UploadOutlined } from '@ant-design/icons';
7 8
 import type { SelectProps } from 'antd';
8 9
 import TextArea from 'antd/es/input/TextArea';
9 10
 import { useQuery } from '@tanstack/react-query';
10 11
 import { getPersonaStyle } from '@/app/api/user/personaService';
11 12
 import type { RcFile } from 'antd/es/upload';
12
-
13
+import PhoneInput from 'react-phone-input-2';
14
+import 'react-phone-input-2/lib/style.css';
13 15
 
14 16
 const CreatePersona: React.FC = () => {
15 17
 
16
-
17 18
     const [name, setName] = useState<string | undefined>("")
19
+    const [phone, setPhone] = useState('');
18 20
     const [options, setOptions] = useState<SelectProps[] | undefined>()
19 21
     const [imageUrl, setImageUrl] = useState<string | null>(null);
20 22
     const [fileList, setFileList] = useState<any[]>([]);
@@ -22,7 +24,8 @@ const CreatePersona: React.FC = () => {
22 24
         isManualKnowledgeEntry: false,
23 25
         isUploadTextDocument: false,
24 26
         isImportWebLink: false,
25
-        isCodeInterpreter: false
27
+        isDallEImageGen: false,
28
+        isSOPAutoLearn: false
26 29
     });
27 30
 
28 31
     const { data: personaStyleList, error, isLoading } = useQuery({
@@ -66,127 +69,170 @@ const CreatePersona: React.FC = () => {
66 69
 
67 70
 
68 71
     return (
69
-        <div className="flex flex-col bg-white">
70
-            <PageTitle title='CREATE PERSONA' />
71
-            <div className='px-4 py-6'>
72
-                <Form layout="vertical">
73
-                    <Form.Item label="Profile Name">
74
-                        <Input
75
-                            style={{ padding: 10 }}
76
-                            value={name}
77
-                            onChange={(e) => setName(e.target.value)}
78
-                        />
79
-                    </Form.Item>
80
-
81
-                    <Form.Item label="Persona Image">
82
-                        <Upload
83
-                            beforeUpload={handleBeforeUpload}
84
-                            fileList={fileList}
85
-                            onRemove={() => {
86
-                                setFileList([]);
87
-                                setImageUrl(null);
88
-                            }}
89
-                            onChange={({ fileList }) => setFileList(fileList)}
90
-                            maxCount={1}
91
-                            accept="image/*"
92
-                            listType="picture"
93
-                        >
94
-                            <button className='border border-gray-300 px-4 py-2 rounded hover:bg-gray-50'>
95
-                                <UploadOutlined /> Click to Upload
96
-                            </button>
97
-                        </Upload>
98
-                        {imageUrl && (
99
-                            <img
100
-                                src={imageUrl}
101
-                                alt="Uploaded Preview"
102
-                                className="mt-2 w-32 h-32 object-cover border rounded"
72
+        <AltLayout header={<PageTitle backButton={true}>Create Persona</PageTitle>}>
73
+            <Row className='px-4'>
74
+                <Col span={24}>
75
+                    <Form layout="vertical">
76
+                        <Form.Item label={<p className='font-bold'>Persona Name</p>}>
77
+                            <Input
78
+                                style={{ padding: 10 }}
79
+                                value={name}
80
+                                onChange={(e) => setName(e.target.value)}
103 81
                             />
104
-                        )}
105
-                    </Form.Item>
106
-
107
-                    <Form.Item label="Role / Department">
108
-                        <Input
109
-                            style={{ padding: 10 }}
110
-                            value={name}
111
-                            onChange={(e) => setName(e.target.value)}
112
-                        />
113
-                    </Form.Item>
114
-
115
-                    <Form.Item label="Description">
116
-                        <TextArea
117
-                            style={{ padding: 10 }}
118
-                            rows={6}
119
-                            value={name}
120
-                            onChange={(e) => setName(e.target.value)}
121
-                        />
122
-                    </Form.Item>
123
-
124
-                    <Form.Item label="Persona Style">
125
-                        <Select
126
-                            mode="multiple"
127
-                            allowClear
128
-                            style={{ width: '100%' }}
129
-                            placeholder="Please select"
130
-                            options={options}
131
-                        />
132
-                    </Form.Item>
133
-
134
-                    <Form.Item label="Greeting Message">
135
-                        <TextArea
136
-                            style={{ padding: 10 }}
137
-                            rows={3}
138
-                            value={name}
139
-                            onChange={(e) => setName(e.target.value)}
140
-                        />
141
-                    </Form.Item>
142
-
143
-                    <Form.Item label="Persona Training Center">
144
-                        <div className='flex flex-col gap-2'>
145
-                            <div className='flex items-center gap-2'>
146
-                                <Switch
147
-                                    checked={enabled.isManualKnowledgeEntry}
148
-                                    onChange={(checked) =>
149
-                                        setEnabled({ ...enabled, isManualKnowledgeEntry: checked })
150
-                                    }
151
-                                />
152
-                                <span className='font-bold'>Manual Knowledge Entry</span>
153
-                            </div>
154
-                            <div className='flex items-center gap-2'>
155
-                                <Switch
156
-                                    checked={enabled.isUploadTextDocument}
157
-                                    onChange={(checked) =>
158
-                                        setEnabled({ ...enabled, isUploadTextDocument: checked })
159
-                                    }
82
+                        </Form.Item>
83
+
84
+                        <Form.Item label={<p className='font-bold'>Avatar Upload</p>}>
85
+                            <Upload
86
+                                beforeUpload={handleBeforeUpload}
87
+                                fileList={fileList}
88
+                                onRemove={() => {
89
+                                    setFileList([]);
90
+                                    setImageUrl(null);
91
+                                }}
92
+                                onChange={({ fileList }) => setFileList(fileList)}
93
+                                maxCount={1}
94
+                                accept="image/*"
95
+                                listType="picture"
96
+                            >
97
+                                <button className='border border-gray-300 px-4 py-2 rounded hover:bg-gray-50'>
98
+                                    <UploadOutlined /> Click to Upload
99
+                                </button>
100
+                            </Upload>
101
+                            {imageUrl && (
102
+                                <img
103
+                                    src={imageUrl}
104
+                                    alt="Uploaded Preview"
105
+                                    className="mt-2 w-32 h-32 object-cover border rounded"
160 106
                                 />
161
-                                <span className='font-bold'>Upload Text Document</span>
162
-                            </div>
163
-                            <div className='flex items-center gap-2'>
164
-                                <Switch
165
-                                    checked={enabled.isImportWebLink}
166
-                                    onChange={(checked) =>
167
-                                        setEnabled({ ...enabled, isImportWebLink: checked })
168
-                                    }
169
-                                />
170
-                                <span className='font-bold'>Import from Web Link</span>
171
-                            </div>
172
-                            <div className='flex items-center gap-2'>
173
-                                <Switch
174
-                                    checked={enabled.isCodeInterpreter}
175
-                                    onChange={(checked) =>
176
-                                        setEnabled({ ...enabled, isCodeInterpreter: checked })
177
-                                    }
178
-                                />
179
-                                <span className='font-bold'>Code Interpreter & Data Analysis</span>
180
-                            </div>
181
-                        </div>
182
-                    </Form.Item>
183
-                    <div className='text-center'>
184
-                        <Button type="primary" className='!w-fit !py-3 !text-xs !font-bold !px-10'>SUBMIT</Button>
185
-                    </div>
186
-                </Form>
187
-            </div>
188
-        </div>
189
-
107
+                            )}
108
+                        </Form.Item>
109
+
110
+                        <Form.Item label={<p className='font-bold'>Role / Department</p>}>
111
+                            <Input
112
+                                style={{ padding: 10 }}
113
+                                value={name}
114
+                                onChange={(e) => setName(e.target.value)}
115
+                            />
116
+                        </Form.Item>
117
+
118
+                        <Form.Item label={<p className='font-bold'>Description</p>}>
119
+                            <TextArea
120
+                                style={{ padding: 10 }}
121
+                                rows={6}
122
+                                value={name}
123
+                                onChange={(e) => setName(e.target.value)}
124
+                            />
125
+                        </Form.Item>
126
+
127
+                        <Form.Item label={<p className='font-bold'>Persona Style</p>}>
128
+                            <Select
129
+                                mode="multiple"
130
+                                allowClear
131
+                                style={{ width: '100%' }}
132
+                                placeholder="Please select"
133
+                                options={options}
134
+                            />
135
+                        </Form.Item>
136
+
137
+                        <Form.Item label={<p className='font-bold'>Greeting Message</p>}>
138
+                            <TextArea
139
+                                style={{ padding: 10 }}
140
+                                rows={3}
141
+                                value={name}
142
+                                onChange={(e) => setName(e.target.value)}
143
+                            />
144
+                        </Form.Item>
145
+
146
+                        <Form.Item label={<p className='font-bold'>Persona Training Center</p>}>
147
+                            <Flex vertical gap={10}>
148
+                                <Flex gap={10} className='!items-center'>
149
+                                    <Switch
150
+                                        checked={enabled.isManualKnowledgeEntry}
151
+                                        onChange={(checked) =>
152
+                                            setEnabled({ ...enabled, isManualKnowledgeEntry: checked })
153
+                                        }
154
+                                    />
155
+                                    <span className='font-bold'>Manual Knowledge Entry</span>
156
+                                </Flex>
157
+                                <div className='flex items-center gap-2'>
158
+                                    <Switch
159
+                                        checked={enabled.isUploadTextDocument}
160
+                                        onChange={(checked) =>
161
+                                            setEnabled({ ...enabled, isUploadTextDocument: checked })
162
+                                        }
163
+                                    />
164
+                                    <span className='font-bold'>Upload Text Document</span>
165
+                                </div>
166
+                                <div className='flex items-center gap-2'>
167
+                                    <Switch
168
+                                        checked={enabled.isImportWebLink}
169
+                                        onChange={(checked) =>
170
+                                            setEnabled({ ...enabled, isImportWebLink: checked })
171
+                                        }
172
+                                    />
173
+                                    <span className="font-bold">
174
+                                        Import from Web Link{' '}
175
+                                        <Tag color="blue" className="font-semibold text-xs px-2 py-[2px] !rounded-full">
176
+                                            Premium
177
+                                        </Tag>
178
+                                    </span>
179
+                                </div>
180
+                                <div className='flex items-center gap-2'>
181
+                                    <Switch
182
+                                        checked={enabled.isDallEImageGen}
183
+                                        onChange={(checked) =>
184
+                                            setEnabled({ ...enabled, isDallEImageGen: checked })
185
+                                        }
186
+                                    />
187
+                                    <span className="font-bold">
188
+                                        DALL-E Image Generation{' '}
189
+                                        <Tag color="blue" className="font-semibold text-xs px-2 py-[2px] !rounded-full">
190
+                                            Premium
191
+                                        </Tag>
192
+                                    </span>
193
+                                </div>
194
+                                <div className='flex items-center gap-2'>
195
+                                    <Switch
196
+                                        checked={enabled.isSOPAutoLearn}
197
+                                        onChange={(checked) =>
198
+                                            setEnabled({ ...enabled, isSOPAutoLearn: checked })
199
+                                        }
200
+                                    />
201
+                                    <span className="font-bold">
202
+                                        Enable SOP Auto-Learning{' '}
203
+                                        <Tag color="blue" className="font-semibold text-xs px-2 py-[2px] !rounded-full">
204
+                                            Premium
205
+                                        </Tag>
206
+                                    </span>
207
+                                </div>
208
+                            </Flex>
209
+                        </Form.Item>
210
+
211
+                        <Form.Item label={<p className='font-bold'>Assign Whatsapp Line</p>}>
212
+                            <PhoneInput
213
+                                country={'my'} // default country
214
+                                value={phone}
215
+                                onChange={(phone, countryData) => {
216
+                                    setPhone(phone);
217
+                                    console.log("Phone Number:", phone);
218
+                                    console.log("Country Info:", countryData);
219
+                                }}
220
+                                enableSearch
221
+                                preferredCountries={['my', 'sg', 'id']}
222
+                                inputStyle={{ width: '100%' }}
223
+                                containerStyle={{ width: '100%' }}
224
+                            />
225
+                        </Form.Item>
226
+
227
+                        <Row >
228
+                            <Col span={24} className='!text-center'>
229
+                                <Button type="primary" className='!w-fit !py-3 !text-xs !font-bold !px-10'>CREATE PERSONA</Button>
230
+                            </Col>
231
+                        </Row>
232
+                    </Form>
233
+                </Col>
234
+            </Row>
235
+        </AltLayout>
190 236
     )
191 237
 }
192 238
 

+ 124
- 100
app/user/persona/page.tsx Datei anzeigen

@@ -1,6 +1,6 @@
1 1
 'use client';
2 2
 
3
-import React, {useEffect, useState} from 'react';
3
+import React, { useEffect, useState } from 'react';
4 4
 import PageTitle from '@/components/ui/PageTitle';
5 5
 import {
6 6
     EyeOutlined,
@@ -11,128 +11,152 @@ import {
11 11
 import type { Persona } from '@/types/persona';
12 12
 import { getPersona } from '@/app/api/user/personaService';
13 13
 import { useQuery } from '@tanstack/react-query';
14
+import PrimaryButton from '@/components/ui/PrimaryButton';
15
+import { Flex, Row, Col, Tag, Button, Table, Layout, Image, Typography } from 'antd';
16
+import type { ColumnsType } from 'antd/es/table';
17
+import { useRouter } from 'next/navigation';
18
+
19
+const {Text, Title} = Typography;
20
+
21
+const columns: ColumnsType<Persona> = [
22
+    {
23
+        title: (
24
+            <Text className="flex items-center gap-1">
25
+                Name <SortAscendingOutlined className="text-xs text-gray-400" />
26
+            </Text>
27
+        ),
28
+        dataIndex: 'name',
29
+        key: 'name',
30
+    },
31
+    {
32
+        title: (
33
+            <Text className="flex items-center gap-1">
34
+                Role <SortAscendingOutlined className="text-xs text-gray-400" />
35
+            </Text>
36
+        ),
37
+        dataIndex: 'role',
38
+        key: 'role',
39
+    },
40
+    {
41
+        title: (
42
+            <Text className="flex items-center gap-1">
43
+                Status <SortAscendingOutlined className="text-xs text-gray-400" />
44
+            </Text>
45
+        ),
46
+        dataIndex: 'active',
47
+        key: 'status',
48
+        align: 'center' as const,
49
+        render: (active: boolean) => (
50
+            <Tag color={active ? 'green' : 'orange'} className="!rounded-full">
51
+                {active ? 'Active' : 'Inactive'}
52
+            </Tag>
53
+        ),
54
+    },
55
+    {
56
+        title: 'Action',
57
+        key: 'action',
58
+        align: 'center' as const,
59
+        render: (_: any, record: Persona) => (
60
+            <Flex justify="center">
61
+                <Button type="text" icon={<EditOutlined />} />
62
+                <Button type="text" icon={<EyeOutlined />} />
63
+            </Flex>
64
+        ),
65
+    },
66
+];
14 67
 
15
-interface PersonaListProps {
16
-  personaData: Persona[] | undefined;
17
-}
18 68
 
19 69
 const PersonaPage: React.FC = () => {
20
-    
21 70
 
22
-    let {data:personas, error, isLoading} = useQuery<Persona[]>({
23
-        queryKey:["getPersona"],
71
+
72
+    const { data: personas, error, isLoading } = useQuery<Persona[]>({
73
+        queryKey: ["getPersona"],
24 74
         queryFn: () => getPersona()
25 75
     })
26 76
 
27
-    if(isLoading) {
77
+    if (isLoading) {
28 78
         return <p>Loading...</p>
29 79
     }
30
-    
31
-    if(!personas) {
80
+
81
+    if (!personas) {
32 82
         return <p></p>
33 83
     }
34 84
 
35 85
     return (
36
-        <main className="flex flex-col bg-white h-full">
37
-            <PageTitle title="PERSONA" />
86
+        <Flex vertical className="!bg-white !h-full">
87
+            <PageTitle>PERSONA</PageTitle>
38 88
             {(personas.length > 0) ? <PersonaList personaData={personas} /> : <PersonaIntro />}
39
-        </main>
89
+        </Flex>
40 90
     );
41 91
 };
42 92
 
43 93
 const PersonaIntro: React.FC = () => {
94
+
95
+    const router = useRouter();
96
+
44 97
     return (
45
-        <div className="flex flex-col px-6 flex-1 justify-center items-center">
46
-            <div
47
-                className="w-full max-w-md text-center border-2 border-cyan-500 rounded-lg px-6 py-10 shadow-lg"
48
-                style={{ boxShadow: '0 10px 100px 30px rgba(59, 130, 246, 0.3)' }} // red-500 with opacity
49
-            >
50
-                <p className="text-lg font-semibold mb-2 text-gray-800">NO PERSONA CREATED YET</p>
51
-                <p className="text-sm text-gray-600 mb-4">Get started by creating your first AI Persona</p>
52
-
53
-                <img
54
-                    src="/ruccan_logo.png"
55
-                    alt="Ruccan Logo"
56
-                    className="mx-auto mb-6"
57
-                    width={150}
58
-                    height={150}
59
-                />
60
-
61
-                <button className="w-fit rounded text-xs px-4 py-2 bg-[#369cc1] hover:bg-[#82c7d7] cursor-pointer text-white font-bold shadow transition-all duration-200">
62
-                    CREATE YOUR FIRST PERSONA
63
-                </button>
64
-            </div>
65
-        </div>
98
+        <Flex vertical flex={1} justify="center" align="center">
99
+            <Row justify="center" style={{ width: '100%', padding: '0 24px' }}>
100
+                <Col xs={24} sm={20} md={16} lg={12} xl={8}>
101
+                    <Flex
102
+                        vertical
103
+                        align="center"
104
+                        className="!text-center"
105
+                        style={{
106
+                            border: '2px solid #06b6d4', // cyan-500
107
+                            borderRadius: '12px',
108
+                            padding: '40px 24px',
109
+                            boxShadow: '0 10px 100px 30px rgba(59, 130, 246, 0.3)',
110
+                            background: 'white',
111
+                        }}
112
+                    >
113
+                        <Title level={5} style={{ marginBottom: 8, color: '#1f2937' }}>NO PERSONA CREATED YET</Title>
114
+                        <Text type="secondary" style={{ marginBottom: 24 }}>
115
+                            Get started by creating your first AI Persona
116
+                        </Text>
117
+
118
+                        <Image
119
+                            src="/Ruccan_Logo2.png"
120
+                            alt="Ruccan Logo"
121
+                            width={150}
122
+                            preview={false}
123
+                            style={{ marginBottom: 24 }}
124
+                        />
125
+
126
+                        <PrimaryButton onClick={() => router.push('/user/persona/create')}>
127
+                            CREATE YOUR FIRST PERSONA
128
+                        </PrimaryButton>
129
+                    </Flex>
130
+                </Col>
131
+            </Row>
132
+        </Flex>
66 133
     )
67 134
 }
135
+interface PersonaListProps {
136
+    personaData: Persona[] | undefined;
137
+}
138
+
139
+const PersonaList: React.FC<PersonaListProps> = ({ personaData }) => {
140
+
141
+    const router = useRouter();
68 142
 
69
-const PersonaList: React.FC<PersonaListProps> = ({personaData}) => {
70 143
     return (
71
-        <div className="overflow-x-auto">
72
-            <div className='flex px-2 py-4'>
73
-                <button className="w-fit rounded text-xs px-4 py-2 bg-[#369cc1] hover:bg-[#82c7d7] cursor-pointer text-white font-bold shadow transition-all duration-200 ms-auto">
74
-                <PlusCircleFilled /> Create New Persona
75
-            </button>
76
-            </div>
77
-            <table className="w-full text-sm text-left text-gray-700 border-collapse">
78
-                <thead>
79
-                    <tr className="bg-gray-100 text-gray-700">
80
-                        <th className="px-4 py-3 font-medium">
81
-                            <div className="flex items-center gap-1">
82
-                                Name <SortAscendingOutlined className="text-xs text-gray-400" />
83
-                            </div>
84
-                        </th>
85
-                        <th className="px-4 py-3 font-medium">
86
-                            <div className="flex items-center gap-1">
87
-                                Role <SortAscendingOutlined className="text-xs text-gray-400" />
88
-                            </div>
89
-                        </th>
90
-                        <th className="px-4 py-3 font-medium">
91
-                            <div className="flex items-center gap-1">
92
-                                Status <SortAscendingOutlined className="text-xs text-gray-400" />
93
-                            </div>
94
-                        </th>
95
-                        <th className="px-4 py-3 font-medium text-center">Action</th>
96
-                    </tr>
97
-                </thead>
98
-                <tbody>
99
-                    {personaData?.map((persona) => (
100
-                        <tr key={persona.id} className="border-t border-gray-200 hover:bg-gray-50">
101
-                            <td className="px-4 py-3">{persona.name}</td>
102
-                            <td className="px-4 py-3">{persona.role}</td>
103
-                            <td className="px-4 py-3">
104
-                                <span
105
-                                    className={`px-2 py-1 rounded-full text-xs font-semibold ${persona.active
106
-                                        ? 'bg-green-100 text-green-600'
107
-                                        : 'bg-amber-600 text-white'
108
-                                        }`}
109
-                                >
110
-                                    {persona.active ? 'Active' : 'Inactive'}
111
-                                </span>
112
-                            </td>
113
-                            <td className="px-4 py-3 text-center">
114
-                                <div className="flex justify-center items-center gap-3 text-gray-600">
115
-                                    <button
116
-                                        className="hover:text-blue-500"
117
-                                        aria-label="Edit"
118
-                                        title="Edit"
119
-                                    >
120
-                                        <EditOutlined />
121
-                                    </button>
122
-                                    <button
123
-                                        className="hover:text-green-600"
124
-                                        aria-label="View"
125
-                                        title="View"
126
-                                    >
127
-                                        <EyeOutlined />
128
-                                    </button>
129
-                                </div>
130
-                            </td>
131
-                        </tr>
132
-                    ))}
133
-                </tbody>
134
-            </table>
135
-        </div>
144
+        <Layout className="!overflow-x-auto">
145
+            <Flex className='!px-2 !py-4 !justify-end'>
146
+                <PrimaryButton onClick={() => { router.push('persona/create') }}>
147
+                    <PlusCircleFilled /> Create New Persona
148
+                </PrimaryButton>
149
+            </Flex>
150
+
151
+            <Table
152
+                className="[&_.ant-table-cell]:!p-2"
153
+                rowKey="id"
154
+                columns={columns}
155
+                dataSource={personaData}
156
+                pagination={false}
157
+                bordered
158
+            />
159
+        </Layout>
136 160
     );
137 161
 };
138 162
 

+ 39
- 0
components/layout/AltLayout.tsx Datei anzeigen

@@ -0,0 +1,39 @@
1
+import React from 'react'
2
+import { Flex, Layout, Row, Col } from 'antd';
3
+import Navigation from '@/components/layout/Navigation'
4
+
5
+type AltLayoutProps = {
6
+    header?: React.ReactNode,
7
+    children?: React.ReactNode,
8
+    footer?: React.ReactNode
9
+}
10
+
11
+const AltLayout: React.FC<AltLayoutProps> = ({ header = <></>, children = <></>, footer =<Navigation /> }:AltLayoutProps) => {
12
+    return (
13
+        <Layout className="!w-full !max-w-[430px] !mx-auto !relative !bg-white">
14
+            <Flex vertical className='!h-screen'>
15
+                <Row>
16
+                    <Col span={24}>
17
+                        {header}
18
+                    </Col>
19
+                </Row>
20
+                <Row className='flex-1 overflow-y-scroll overflow-x-hidden'>
21
+                    <Col span={24}>
22
+                        <Flex vertical className="!bg-white">
23
+                            {/* Chat messages */}
24
+                            <div className="flex-1 overflow-y-auto px-4 py-6 space-y-4">
25
+                                {children}
26
+                            </div>
27
+
28
+                        </Flex>
29
+                    </Col>
30
+                </Row>
31
+                <div>
32
+                    {footer}
33
+                </div>
34
+            </Flex>
35
+        </Layout>
36
+    )
37
+}
38
+
39
+export default AltLayout

+ 4
- 2
components/layout/Header.tsx Datei anzeigen

@@ -2,6 +2,8 @@
2 2
 
3 3
 import { useState } from 'react'
4 4
 import { MenuOutlined } from '@ant-design/icons'
5
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
6
+import { faBars, faBell } from '@fortawesome/free-solid-svg-icons'
5 7
 import { Button, Space } from 'antd'
6 8
 import Sidebar from '@/components/layout/Sidebar'
7 9
 import BellNotification from '@/components/ui/BellNotification'
@@ -14,7 +16,7 @@ const Header = () => {
14 16
       {/* Sidebar Trigger */}
15 17
       <Button
16 18
         type="text"
17
-        icon={<MenuOutlined />}
19
+        icon={<FontAwesomeIcon icon={faBars} />}
18 20
         onClick={() => setShowSideBar(true)}
19 21
         className="text-lg text-gray-700"
20 22
       />
@@ -24,7 +26,7 @@ const Header = () => {
24 26
 
25 27
       {/* Logo + Brand */}
26 28
       <div className="flex items-center ms-3">
27
-        <img src="/ruccan_logo.png" alt="Ruccan Logo" className="h-8 w-auto" />
29
+        <img src="/ruccan_logo.png" alt="Ruccan Logo" className="h-6 w-auto" />
28 30
         <span className="text-black font-bold text-lg ms-3">Ruccan.com</span>
29 31
       </div>
30 32
 

+ 43
- 0
components/layout/Navigation.tsx Datei anzeigen

@@ -0,0 +1,43 @@
1
+import React from 'react'
2
+import { Menu } from 'antd'
3
+import {
4
+    HomeOutlined,
5
+    MessageOutlined,
6
+    UserOutlined,
7
+} from '@ant-design/icons'
8
+import { useRouter, usePathname } from 'next/navigation'
9
+
10
+const navItems = [
11
+    {
12
+        key: '/user/',
13
+        icon: <HomeOutlined />,
14
+        label: 'Home',
15
+    },
16
+    {
17
+        key: '/user/chat',
18
+        icon: <MessageOutlined />,
19
+        label: 'Chat',
20
+    },
21
+    {
22
+        key: '/user/profile',
23
+        icon: <UserOutlined />,
24
+        label: 'Profile',
25
+    },
26
+];
27
+
28
+const Navigation = () => {
29
+    const router = useRouter()
30
+    const pathname = usePathname()
31
+
32
+    return (
33
+        <Menu
34
+            mode="horizontal"
35
+            selectedKeys={[pathname]}
36
+            onClick={({ key }) => router.push(key)}
37
+            items={navItems}
38
+            className="custom-center-menu"
39
+        />
40
+    )
41
+}
42
+
43
+export default Navigation

+ 115
- 105
components/layout/Sidebar.tsx Datei anzeigen

@@ -1,21 +1,16 @@
1 1
 'use client'
2 2
 
3
-import { Drawer, Menu, Divider, Avatar, Typography } from 'antd'
3
+import { Drawer, Menu, Avatar, Typography, Image, Flex, Row, Col } from 'antd'
4 4
 import {
5
-  HomeOutlined,
6
-  MessageOutlined,
7 5
   UserOutlined,
8
-  BookOutlined,
9
-  ApiOutlined,
10
-  WalletOutlined,
11
-  SettingOutlined,
12
-  QuestionCircleOutlined,
13
-  LogoutOutlined,
14 6
   LeftOutlined
15 7
 } from '@ant-design/icons'
16 8
 import { useRouter } from 'next/navigation'
17 9
 import type { SidebarProps } from '@/types/sidebar'
18 10
 
11
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
12
+import { faHome, faComment, faUserAlt, faQuestionCircle, faPlug, faWallet, faCog, faSignOut } from '@fortawesome/free-solid-svg-icons'
13
+
19 14
 const { Text } = Typography
20 15
 
21 16
 const Sidebar = ({ open = false, setOpen = () => { } }: SidebarProps) => {
@@ -56,107 +51,122 @@ const Sidebar = ({ open = false, setOpen = () => { } }: SidebarProps) => {
56 51
       </button>
57 52
 
58 53
       {/* Top: Avatar/Profile */}
59
-      <div className="flex items-center gap-3 px-4 py-4 border-b border-gray-200">
60
-        <Avatar size="large" icon={<UserOutlined />} />
61
-        <div>
62
-          <div className="text-xs text-gray-500">PRODUCT MANAGER</div>
63
-          <Text strong>Amirul Baharuddin</Text>
64
-        </div>
65
-      </div>
54
+      <Flex className="!items-center !gap-3 !px-4 !py-4 !border-b !border-gray-200">
55
+        <Image
56
+          src="/user.svg"
57
+          width={25}
58
+          preview={false}
59
+          className="!mx-auto !my-auto"
60
+          alt="Ruccan AI Labs"
61
+        />
62
+        <Row className='!ps-1'>
63
+          <Col span={24}>
64
+            <Text className="!text-xs !text-gray-500">PRODUCT MANAGER</Text>
65
+          </Col>
66
+          <Col span={24}>
67
+            <Text strong>Amirul Baharuddin</Text>
68
+          </Col>
69
+        </Row>
70
+      </Flex>
66 71
 
67 72
       {/* Main Menu */}
68
-      <div className='border-b border-gray-200'>
69
-        <p className='px-7 pt-3 text-gray-500 text-xs text-bold'>MAIN</p>
70
-        <Menu
71
-          mode="inline"
72
-          onClick={handleClick}
73
-          items={[
74
-            {
75
-              key: '/dashboard',
76
-              icon: <HomeOutlined />,
77
-              label: 'Dashboard',
78
-            },
79
-            {
80
-              key: '/chat',
81
-              icon: <MessageOutlined />,
82
-              label: 'Ruccan Chat',
83
-            },
84
-            {
85
-              key: '/persona',
86
-              icon: <UserOutlined />,
87
-              label: 'Persona',
88
-            },
89
-            {
90
-              key: '/knowledge',
91
-              icon: <BookOutlined />,
92
-              label: 'Knowledge',
93
-            },
94
-            {
95
-              key: '/integration',
96
-              icon: <ApiOutlined />,
97
-              label: 'Integration',
98
-            },
99
-            {
100
-              key: '/wallet',
101
-              icon: <WalletOutlined />,
102
-              label: 'Wallet',
103
-            },
104
-          ]}
105
-        />
106
-      </div>
73
+      <Row className='border-b border-gray-200'>
74
+        <Col span={24} className='!pt-3'>
75
+          <Text className='!px-7 !text-gray-500 !text-xs !text-bold'>MAIN</Text>
76
+          <Menu
77
+            mode="inline"
78
+            className="!border-r-0"
79
+            onClick={handleClick}
80
+            items={[
81
+              {
82
+                key: '/user',
83
+                icon: <FontAwesomeIcon icon={faHome} />,
84
+                label: 'Dashboard',
85
+              },
86
+              {
87
+                key: '/user/chat',
88
+                icon: <FontAwesomeIcon icon={faComment} />,
89
+                label: 'Ruccan Chat',
90
+              },
91
+              {
92
+                key: '/user/persona',
93
+                icon: <FontAwesomeIcon icon={faUserAlt} />,
94
+                label: 'Persona',
95
+              },
96
+              {
97
+                key: '/user/knowledge',
98
+                icon: <FontAwesomeIcon icon={faQuestionCircle} />,
99
+                label: 'Knowledge',
100
+              },
101
+              {
102
+                key: '/user/integration',
103
+                icon: <FontAwesomeIcon icon={faPlug} />,
104
+                label: 'Integration',
105
+              },
106
+              {
107
+                key: '/user/wallet',
108
+                icon: <FontAwesomeIcon icon={faWallet} />,
109
+                label: 'Wallet',
110
+              },
111
+            ]}
112
+          />
113
+        </Col>
114
+      </Row>
107 115
 
108 116
       {/* Settings & Others */}
109
-      <div>
110
-        <p className='px-7 pt-3 text-gray-500 text-xs text-bold'>SETTINGS</p>
111
-        <Menu
112
-          mode="inline"
113
-          onClick={handleClick}
114
-          items={[
115
-            {
116
-              key: 'settings-parent',
117
-              icon: <SettingOutlined />,
118
-              label: 'Settings',
119
-              children: [
120
-                {
121
-                  key: '/settings/general',
122
-                  icon: <SettingOutlined />, // General settings still makes sense
123
-                  label: 'General',
124
-                },
125
-                {
126
-                  key: '/settings/profile',
127
-                  icon: <UserOutlined />, // Profile = user-related
128
-                  label: 'Profile',
129
-                },
130
-                {
131
-                  key: '/settings/chat-preference',
132
-                  icon: <MessageOutlined />, // Chat preference = chat/message
133
-                  label: 'Chat Preference',
134
-                }
135
-              ]
136
-            }
137
-          ]}
138
-        />
139
-      </div>
140
-      <div>
141
-        <Menu
142
-          mode="inline"
143
-          onClick={handleClick}
144
-          style={{ position: "absolute", bottom: 0, left: 0 }}
145
-          items={[
117
+      <Row>
118
+        <Col span={24} className='!pt-3'>
119
+          <Text className='!px-7 !text-gray-500 !text-xs !text-bold'>SETTINGS</Text>
120
+          <Menu
121
+            mode="inline"
122
+            className="!border-r-0"
123
+            onClick={handleClick}
124
+            items={[
125
+              {
126
+                key: 'settings-parent',
127
+                icon: <FontAwesomeIcon icon={faCog} />,
128
+                label: 'Settings',
129
+                children: [
130
+                  {
131
+                    key: '/settings/general',
132
+                    icon: <FontAwesomeIcon icon={faCog} />,
133
+                    label: 'General',
134
+                  },
135
+                  {
136
+                    key: '/settings/profile',
137
+                    icon: <FontAwesomeIcon icon={faUserAlt} />,
138
+                    label: 'Profile',
139
+                  },
140
+                  {
141
+                    key: '/settings/chat-preference',
142
+                    icon: <FontAwesomeIcon icon={faComment} />,
143
+                    label: 'Chat Preference',
144
+                  }
145
+                ]
146
+              }
147
+            ]}
148
+          />
149
+        </Col>
150
+      </Row>
151
+      <Menu
152
+        mode="inline"
153
+        className="!border-r-0"
154
+        onClick={handleClick}
155
+        style={{ position: "absolute", bottom: 0, left: 0 }}
156
+        items={[
146 157
 
147
-            {
148
-              key: '/help',
149
-              icon: <QuestionCircleOutlined />,
150
-              label: 'Help',
151
-            },
152
-            {
153
-              key: '/logout',
154
-              icon: <LogoutOutlined style={{ color: "red" }} />,
155
-              label: <p className='text-red-400 font-bold'>Logout</p>,
156
-            },
157
-          ]}
158
-        />
159
-      </div>
158
+          {
159
+            key: '/help',
160
+            icon: <FontAwesomeIcon icon={faQuestionCircle} />,
161
+            label: 'Help',
162
+          },
163
+          {
164
+            key: '/logout',
165
+            icon: <FontAwesomeIcon icon={faSignOut} className='!text-red-600' />,
166
+            label: <p className='text-red-400 font-bold'>Logout</p>,
167
+          },
168
+        ]}
169
+      />
160 170
     </Drawer>
161 171
   )
162 172
 }

+ 8
- 10
components/ui/BellNotification.tsx Datei anzeigen

@@ -1,10 +1,8 @@
1 1
 'use client'
2 2
 
3
-import {
4
-  BellOutlined,
5
-} from '@ant-design/icons'
6
-import { Badge, Dropdown, Space, List, Typography } from 'antd'
7
-import type { MenuProps } from 'antd'
3
+import { Badge, Dropdown, Space, List, Typography, Flex } from 'antd'
4
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
5
+import { faBell } from '@fortawesome/free-solid-svg-icons'
8 6
 
9 7
 const { Text } = Typography
10 8
 
@@ -40,10 +38,10 @@ const NotificationList = () => (
40 38
           <List.Item.Meta
41 39
             title={<Text strong>{item.title}</Text>}
42 40
             description={
43
-              <div className="flex justify-between text-xs text-gray-500">
44
-                <span>{item.description}</span>
45
-                <span className="whitespace-nowrap">{item.time}</span>
46
-              </div>
41
+              <Flex className="!justify-between !text-gray-500">
42
+                <Text className='!text-xs'>{item.description}</Text>
43
+                <Text className="!whitespace-nowrap !text-xs">{item.time}</Text>
44
+              </Flex>
47 45
             }
48 46
           />
49 47
         </List.Item>
@@ -62,7 +60,7 @@ const BellNotification = () => {
62 60
     >
63 61
       <Space className="cursor-pointer relative">
64 62
         <Badge count={notifications.length} size="small" offset={[-2, 2]}>
65
-          <BellOutlined style={{ fontSize: 20, color: 'black' }} />
63
+          <FontAwesomeIcon icon={faBell} style={{ fontSize: 20, color: 'black' }} />
66 64
         </Badge>
67 65
       </Space>
68 66
     </Dropdown>

+ 2
- 2
components/ui/InputList.tsx Datei anzeigen

@@ -7,7 +7,7 @@ const InputList: React.FC = () => {
7 7
     <div className='mt-2'>
8 8
       <Form.List name="weblinks">
9 9
         {(fields, { add, remove }) => (
10
-          <Space direction="vertical" style={{ width: '100%' }}>
10
+          <div style={{ width: '100%' }}>
11 11
             {fields.map(({ key, name, ...restField }) => (
12 12
               <Space key={key} align="baseline" style={{ width: '100%' }}>
13 13
                 <Form.Item
@@ -37,7 +37,7 @@ const InputList: React.FC = () => {
37 37
                 </Button>
38 38
               </Form.Item>
39 39
             )}
40
-          </Space>
40
+          </div>
41 41
         )}
42 42
       </Form.List>
43 43
     </div>

+ 13
- 8
components/ui/LoadingMeter.tsx Datei anzeigen

@@ -1,4 +1,7 @@
1 1
 import React from 'react';
2
+import { Flex, Typography, Divider, Layout, Button, Row, Col, Image } from 'antd';
3
+
4
+const { Title, Text } = Typography;
2 5
 
3 6
 type LoadingMeterProps = {
4 7
     title: string,
@@ -11,24 +14,26 @@ const LoadingMeter: React.FC<LoadingMeterProps> = ({ title = "Title", progress =
11 14
 
12 15
     const barProgress = (progress * 100) / total;
13 16
 
14
-    if(progress > total) return <p className='text-red-500'>Progress is more than total</p>
17
+    if (progress > total) return <Text className='!text-red-500'>Progress is more than total</Text>
15 18
 
16 19
     return (
17
-        <div className="flex flex-col gap-2 w-full max-w-md mb-2">
20
+        <Flex vertical gap={2} className="!w-full !max-w-md !mb-2">
18 21
             {/* Header Row */}
19
-            <div className="flex flex-row justify-between text-sm font-medium text-gray-700">
20
-                <span>{`${title}: `}</span>
21
-                <span>{`${progress}/${total}`}</span>
22
-            </div>
22
+            <Row className="flex flex-row justify-between text-sm font-medium text-gray-700">
23
+                <Col span={24}>
24
+                    <Text className='!text-xs'>{`${title}: `}</Text>
25
+                    <Text className='!text-xs'>{`${progress}/${total}`}</Text>
26
+                </Col>
27
+            </Row>
23 28
 
24 29
             {/* Progress Bar */}
25 30
             <div className="w-full h-3 bg-gray-200 rounded-full border overflow-hidden">
26 31
                 <div
27
-                    className="h-full bg-[#b64fb1] rounded-full transition-all duration-500"
32
+                    className="h-full bg-[#602FD0] rounded-full transition-all duration-500"
28 33
                     style={{ width: `${barProgress}%` }}
29 34
                 ></div>
30 35
             </div>
31
-        </div>
36
+        </Flex>
32 37
     );
33 38
 };
34 39
 

+ 53
- 13
components/ui/PageTitle.tsx Datei anzeigen

@@ -1,20 +1,60 @@
1
-import React from 'react'
2
-import { Typography } from 'antd';
1
+import React from 'react';
2
+import { Typography, Flex, Button } from 'antd';
3
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
4
+import { faArrowLeft } from '@fortawesome/free-solid-svg-icons';
5
+import { useRouter } from 'next/navigation';
6
+
3 7
 const { Title } = Typography;
4 8
 
5 9
 type PageTitleProps = {
6
-    title: string
7
-}
10
+    children?: React.ReactNode;
11
+    position?: 'left' | 'center' | 'right';
12
+    backButton?: boolean;
13
+};
14
+
15
+const PageTitle: React.FC<PageTitleProps> = ({
16
+    children = <></>,
17
+    position = 'center',
18
+    backButton = false,
19
+}) => {
20
+    const router = useRouter();
21
+
22
+    const justifyMap: Record<string, 'flex-start' | 'center' | 'flex-end'> = {
23
+        left: 'flex-start',
24
+        center: 'center',
25
+        right: 'flex-end',
26
+    };
8 27
 
9
-const PageTitle: React.FC<PageTitleProps> = ({ title }) => {
10 28
     return (
11
-        <div className='bg-[#480066] flex justify-center py-3'>
12
-            <Title level={4} style={{margin:0, color:"white"}}>
13
-                {title}
14
-            </Title>
29
+        <Flex
30
+            align="center"
31
+            justify="space-between"
32
+            className="!bg-[#602FD0] !py-3 !px-5"
33
+        >
34
+            {/* Back Button */}
35
+            {backButton ? (
36
+                <Button
37
+                    type="text"
38
+                    icon={<FontAwesomeIcon icon={faArrowLeft} />}
39
+                    onClick={() => router.back()}
40
+                    style={{ fontSize: 20 }}
41
+                    className="!text-white"
42
+                />
43
+            ) : (
44
+                <div style={{ width: 40 }} /> // Placeholder to keep spacing
45
+            )}
46
+
47
+            {/* Title */}
48
+            <Flex style={{ flex: 1 }} justify={justifyMap[position]}>
49
+                <Title level={4} style={{ margin: 0, color: 'white' }}>
50
+                    {children}
51
+                </Title>
52
+            </Flex>
15 53
 
16
-        </div>
17
-    )
18
-}
54
+            {/* Right Spacer to balance layout */}
55
+            <div style={{ width: 40 }} />
56
+        </Flex>
57
+    );
58
+};
19 59
 
20
-export default PageTitle
60
+export default PageTitle;

+ 45
- 0
components/ui/PrimaryButton.tsx Datei anzeigen

@@ -0,0 +1,45 @@
1
+'use client';
2
+
3
+import React from 'react';
4
+import { Button } from 'antd';
5
+import { useThemeToken } from '@/hooks/useThemeToken';
6
+import classNames from 'classnames';
7
+
8
+type PrimaryButtonProps = {
9
+    onClick?: () => void;
10
+    children: React.ReactNode;
11
+    customStyle?: string;
12
+    outline?: boolean;
13
+};
14
+
15
+const PrimaryButton: React.FC<PrimaryButtonProps> = ({
16
+    onClick = () => { },
17
+    children,
18
+    customStyle = '',
19
+    outline = false,
20
+}: PrimaryButtonProps) => {
21
+
22
+    const token = useThemeToken();
23
+
24
+    console.log(token.colorPrimary2); 
25
+
26
+    const baseClasses = 'mt-2 !rounded-full !px-6 !py-4';
27
+    const filledClasses = '!border-0 !bg-[#602FD0] !text-[#fff] hover:!bg-[#271550]';
28
+    const outlineClasses = '!border !bg-[#fff] !text-[#602FD0] hover:!bg-[#602FD0] hover:!text-[#fff]';
29
+
30
+    return (
31
+        <Button
32
+            size="small"
33
+            className={classNames(
34
+                baseClasses,
35
+                outline ? outlineClasses : filledClasses,
36
+                customStyle
37
+            )}
38
+            onClick={onClick}
39
+        >
40
+            {children}
41
+        </Button>
42
+    );
43
+};
44
+
45
+export default PrimaryButton;

+ 8
- 0
hooks/useThemeToken.ts Datei anzeigen

@@ -0,0 +1,8 @@
1
+// hooks/useThemeToken.ts
2
+'use client';
3
+import { theme as antdTheme } from 'antd';
4
+
5
+export const useThemeToken = () => {
6
+    const { token } = antdTheme.useToken();
7
+    return token;
8
+};

+ 157
- 28
package-lock.json Datei anzeigen

@@ -9,20 +9,27 @@
9 9
       "version": "0.1.0",
10 10
       "dependencies": {
11 11
         "@ant-design/icons": "^6.0.0",
12
+        "@fortawesome/fontawesome-svg-core": "^6.7.2",
13
+        "@fortawesome/free-brands-svg-icons": "^6.7.2",
14
+        "@fortawesome/free-regular-svg-icons": "^6.7.2",
15
+        "@fortawesome/free-solid-svg-icons": "^6.7.2",
16
+        "@fortawesome/react-fontawesome": "^0.2.2",
12 17
         "@tanstack/react-query": "^5.81.5",
13 18
         "antd": "^5.26.3",
19
+        "classnames": "^2.5.1",
14 20
         "lucide-react": "^0.525.0",
15 21
         "next": "15.3.4",
16 22
         "next-auth": "^4.24.11",
17
-        "react": "^19.0.0",
18
-        "react-dom": "^19.0.0"
23
+        "react": "^18.3.1",
24
+        "react-dom": "^18.3.1",
25
+        "react-phone-input-2": "^2.15.1"
19 26
       },
20 27
       "devDependencies": {
21 28
         "@eslint/eslintrc": "^3",
22 29
         "@tailwindcss/postcss": "^4",
23 30
         "@types/node": "^20",
24
-        "@types/react": "^19",
25
-        "@types/react-dom": "^19",
31
+        "@types/react": "^18.3.23",
32
+        "@types/react-dom": "^18.3.7",
26 33
         "autoprefixer": "^10.4.21",
27 34
         "eslint": "^9",
28 35
         "eslint-config-next": "15.3.4",
@@ -380,6 +387,76 @@
380 387
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
381 388
       }
382 389
     },
390
+    "node_modules/@fortawesome/fontawesome-common-types": {
391
+      "version": "6.7.2",
392
+      "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz",
393
+      "integrity": "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==",
394
+      "license": "MIT",
395
+      "engines": {
396
+        "node": ">=6"
397
+      }
398
+    },
399
+    "node_modules/@fortawesome/fontawesome-svg-core": {
400
+      "version": "6.7.2",
401
+      "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz",
402
+      "integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==",
403
+      "license": "MIT",
404
+      "dependencies": {
405
+        "@fortawesome/fontawesome-common-types": "6.7.2"
406
+      },
407
+      "engines": {
408
+        "node": ">=6"
409
+      }
410
+    },
411
+    "node_modules/@fortawesome/free-brands-svg-icons": {
412
+      "version": "6.7.2",
413
+      "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.7.2.tgz",
414
+      "integrity": "sha512-zu0evbcRTgjKfrr77/2XX+bU+kuGfjm0LbajJHVIgBWNIDzrhpRxiCPNT8DW5AdmSsq7Mcf9D1bH0aSeSUSM+Q==",
415
+      "license": "(CC-BY-4.0 AND MIT)",
416
+      "dependencies": {
417
+        "@fortawesome/fontawesome-common-types": "6.7.2"
418
+      },
419
+      "engines": {
420
+        "node": ">=6"
421
+      }
422
+    },
423
+    "node_modules/@fortawesome/free-regular-svg-icons": {
424
+      "version": "6.7.2",
425
+      "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.7.2.tgz",
426
+      "integrity": "sha512-7Z/ur0gvCMW8G93dXIQOkQqHo2M5HLhYrRVC0//fakJXxcF1VmMPsxnG6Ee8qEylA8b8Q3peQXWMNZ62lYF28g==",
427
+      "license": "(CC-BY-4.0 AND MIT)",
428
+      "dependencies": {
429
+        "@fortawesome/fontawesome-common-types": "6.7.2"
430
+      },
431
+      "engines": {
432
+        "node": ">=6"
433
+      }
434
+    },
435
+    "node_modules/@fortawesome/free-solid-svg-icons": {
436
+      "version": "6.7.2",
437
+      "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.2.tgz",
438
+      "integrity": "sha512-GsBrnOzU8uj0LECDfD5zomZJIjrPhIlWU82AHwa2s40FKH+kcxQaBvBo3Z4TxyZHIyX8XTDxsyA33/Vx9eFuQA==",
439
+      "license": "(CC-BY-4.0 AND MIT)",
440
+      "dependencies": {
441
+        "@fortawesome/fontawesome-common-types": "6.7.2"
442
+      },
443
+      "engines": {
444
+        "node": ">=6"
445
+      }
446
+    },
447
+    "node_modules/@fortawesome/react-fontawesome": {
448
+      "version": "0.2.2",
449
+      "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz",
450
+      "integrity": "sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==",
451
+      "license": "MIT",
452
+      "dependencies": {
453
+        "prop-types": "^15.8.1"
454
+      },
455
+      "peerDependencies": {
456
+        "@fortawesome/fontawesome-svg-core": "~1 || ~6",
457
+        "react": ">=16.3"
458
+      }
459
+    },
383 460
     "node_modules/@humanfs/core": {
384 461
       "version": "0.19.1",
385 462
       "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -1649,24 +1726,32 @@
1649 1726
         "undici-types": "~6.21.0"
1650 1727
       }
1651 1728
     },
1729
+    "node_modules/@types/prop-types": {
1730
+      "version": "15.7.15",
1731
+      "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
1732
+      "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
1733
+      "dev": true,
1734
+      "license": "MIT"
1735
+    },
1652 1736
     "node_modules/@types/react": {
1653
-      "version": "19.1.8",
1654
-      "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz",
1655
-      "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==",
1737
+      "version": "18.3.23",
1738
+      "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz",
1739
+      "integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==",
1656 1740
       "dev": true,
1657 1741
       "license": "MIT",
1658 1742
       "dependencies": {
1743
+        "@types/prop-types": "*",
1659 1744
         "csstype": "^3.0.2"
1660 1745
       }
1661 1746
     },
1662 1747
     "node_modules/@types/react-dom": {
1663
-      "version": "19.1.6",
1664
-      "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz",
1665
-      "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==",
1748
+      "version": "18.3.7",
1749
+      "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
1750
+      "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
1666 1751
       "dev": true,
1667 1752
       "license": "MIT",
1668 1753
       "peerDependencies": {
1669
-        "@types/react": "^19.0.0"
1754
+        "@types/react": "^18.0.0"
1670 1755
       }
1671 1756
     },
1672 1757
     "node_modules/@typescript-eslint/eslint-plugin": {
@@ -4690,7 +4775,6 @@
4690 4775
       "version": "4.0.0",
4691 4776
       "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
4692 4777
       "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
4693
-      "dev": true,
4694 4778
       "license": "MIT"
4695 4779
     },
4696 4780
     "node_modules/js-yaml": {
@@ -5064,6 +5148,18 @@
5064 5148
         "url": "https://github.com/sponsors/sindresorhus"
5065 5149
       }
5066 5150
     },
5151
+    "node_modules/lodash.debounce": {
5152
+      "version": "4.0.8",
5153
+      "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
5154
+      "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
5155
+      "license": "MIT"
5156
+    },
5157
+    "node_modules/lodash.memoize": {
5158
+      "version": "4.1.2",
5159
+      "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
5160
+      "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==",
5161
+      "license": "MIT"
5162
+    },
5067 5163
     "node_modules/lodash.merge": {
5068 5164
       "version": "4.6.2",
5069 5165
       "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -5071,11 +5167,22 @@
5071 5167
       "dev": true,
5072 5168
       "license": "MIT"
5073 5169
     },
5170
+    "node_modules/lodash.reduce": {
5171
+      "version": "4.6.0",
5172
+      "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz",
5173
+      "integrity": "sha512-6raRe2vxCYBhpBu+B+TtNGUzah+hQjVdu3E17wfusjyrXBka2nBS8OH/gjVZ5PvHOhWmIZTYri09Z6n/QfnNMw==",
5174
+      "license": "MIT"
5175
+    },
5176
+    "node_modules/lodash.startswith": {
5177
+      "version": "4.2.1",
5178
+      "resolved": "https://registry.npmjs.org/lodash.startswith/-/lodash.startswith-4.2.1.tgz",
5179
+      "integrity": "sha512-XClYR1h4/fJ7H+mmCKppbiBmljN/nGs73iq2SjCT9SF4CBPoUHzLvWmH1GtZMhMBZSiRkHXfeA2RY1eIlJ75ww==",
5180
+      "license": "MIT"
5181
+    },
5074 5182
     "node_modules/loose-envify": {
5075 5183
       "version": "1.4.0",
5076 5184
       "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
5077 5185
       "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
5078
-      "dev": true,
5079 5186
       "license": "MIT",
5080 5187
       "dependencies": {
5081 5188
         "js-tokens": "^3.0.0 || ^4.0.0"
@@ -5406,7 +5513,6 @@
5406 5513
       "version": "4.1.1",
5407 5514
       "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
5408 5515
       "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
5409
-      "dev": true,
5410 5516
       "license": "MIT",
5411 5517
       "engines": {
5412 5518
         "node": ">=0.10.0"
@@ -5773,7 +5879,6 @@
5773 5879
       "version": "15.8.1",
5774 5880
       "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
5775 5881
       "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
5776
-      "dev": true,
5777 5882
       "license": "MIT",
5778 5883
       "dependencies": {
5779 5884
         "loose-envify": "^1.4.0",
@@ -6425,33 +6530,54 @@
6425 6530
       }
6426 6531
     },
6427 6532
     "node_modules/react": {
6428
-      "version": "19.1.0",
6429
-      "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
6430
-      "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
6533
+      "version": "18.3.1",
6534
+      "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
6535
+      "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
6431 6536
       "license": "MIT",
6537
+      "dependencies": {
6538
+        "loose-envify": "^1.1.0"
6539
+      },
6432 6540
       "engines": {
6433 6541
         "node": ">=0.10.0"
6434 6542
       }
6435 6543
     },
6436 6544
     "node_modules/react-dom": {
6437
-      "version": "19.1.0",
6438
-      "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
6439
-      "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
6545
+      "version": "18.3.1",
6546
+      "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
6547
+      "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
6440 6548
       "license": "MIT",
6441 6549
       "dependencies": {
6442
-        "scheduler": "^0.26.0"
6550
+        "loose-envify": "^1.1.0",
6551
+        "scheduler": "^0.23.2"
6443 6552
       },
6444 6553
       "peerDependencies": {
6445
-        "react": "^19.1.0"
6554
+        "react": "^18.3.1"
6446 6555
       }
6447 6556
     },
6448 6557
     "node_modules/react-is": {
6449 6558
       "version": "16.13.1",
6450 6559
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
6451 6560
       "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
6452
-      "dev": true,
6453 6561
       "license": "MIT"
6454 6562
     },
6563
+    "node_modules/react-phone-input-2": {
6564
+      "version": "2.15.1",
6565
+      "resolved": "https://registry.npmjs.org/react-phone-input-2/-/react-phone-input-2-2.15.1.tgz",
6566
+      "integrity": "sha512-W03abwhXcwUoq+vUFvC6ch2+LJYMN8qSOiO889UH6S7SyMCQvox/LF3QWt+cZagZrRdi5z2ON3omnjoCUmlaYw==",
6567
+      "license": "MIT",
6568
+      "dependencies": {
6569
+        "classnames": "^2.2.6",
6570
+        "lodash.debounce": "^4.0.8",
6571
+        "lodash.memoize": "^4.1.2",
6572
+        "lodash.reduce": "^4.6.0",
6573
+        "lodash.startswith": "^4.2.1",
6574
+        "prop-types": "^15.7.2"
6575
+      },
6576
+      "peerDependencies": {
6577
+        "react": "^16.12.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0",
6578
+        "react-dom": "^16.12.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0"
6579
+      }
6580
+    },
6455 6581
     "node_modules/reflect.getprototypeof": {
6456 6582
       "version": "1.0.10",
6457 6583
       "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
@@ -6634,10 +6760,13 @@
6634 6760
       }
6635 6761
     },
6636 6762
     "node_modules/scheduler": {
6637
-      "version": "0.26.0",
6638
-      "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
6639
-      "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
6640
-      "license": "MIT"
6763
+      "version": "0.23.2",
6764
+      "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
6765
+      "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
6766
+      "license": "MIT",
6767
+      "dependencies": {
6768
+        "loose-envify": "^1.1.0"
6769
+      }
6641 6770
     },
6642 6771
     "node_modules/scroll-into-view-if-needed": {
6643 6772
       "version": "3.1.0",

+ 11
- 4
package.json Datei anzeigen

@@ -10,20 +10,27 @@
10 10
   },
11 11
   "dependencies": {
12 12
     "@ant-design/icons": "^6.0.0",
13
+    "@fortawesome/fontawesome-svg-core": "^6.7.2",
14
+    "@fortawesome/free-brands-svg-icons": "^6.7.2",
15
+    "@fortawesome/free-regular-svg-icons": "^6.7.2",
16
+    "@fortawesome/free-solid-svg-icons": "^6.7.2",
17
+    "@fortawesome/react-fontawesome": "^0.2.2",
13 18
     "@tanstack/react-query": "^5.81.5",
14 19
     "antd": "^5.26.3",
20
+    "classnames": "^2.5.1",
15 21
     "lucide-react": "^0.525.0",
16 22
     "next": "15.3.4",
17 23
     "next-auth": "^4.24.11",
18
-    "react": "^19.0.0",
19
-    "react-dom": "^19.0.0"
24
+    "react": "^18.3.1",
25
+    "react-dom": "^18.3.1",
26
+    "react-phone-input-2": "^2.15.1"
20 27
   },
21 28
   "devDependencies": {
22 29
     "@eslint/eslintrc": "^3",
23 30
     "@tailwindcss/postcss": "^4",
24 31
     "@types/node": "^20",
25
-    "@types/react": "^19",
26
-    "@types/react-dom": "^19",
32
+    "@types/react": "^18.3.23",
33
+    "@types/react-dom": "^18.3.7",
27 34
     "autoprefixer": "^10.4.21",
28 35
     "eslint": "^9",
29 36
     "eslint-config-next": "15.3.4",

BIN
public/Ruccan_Logo2.png Datei anzeigen


BIN
public/ruccan_logo.png Datei anzeigen


+ 3
- 0
public/user.svg Datei anzeigen

@@ -0,0 +1,3 @@
1
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2
+  <path id="FontAwsome_user-alt_" data-name="FontAwsome (user-alt)" d="M12,13.5A6.75,6.75,0,1,0,5.25,6.75,6.752,6.752,0,0,0,12,13.5ZM18,15H15.417a8.16,8.16,0,0,1-6.834,0H6a6,6,0,0,0-6,6v.75A2.251,2.251,0,0,0,2.25,24h19.5A2.251,2.251,0,0,0,24,21.75V21A6,6,0,0,0,18,15Z"/>
3
+</svg>

+ 1
- 0
tsconfig.json Datei anzeigen

@@ -12,6 +12,7 @@
12 12
     "resolveJsonModule": true,
13 13
     "isolatedModules": true,
14 14
     "jsx": "preserve",
15
+    "typeRoots": ["./types", "./node_modules/@types"],
15 16
     "incremental": true,
16 17
     "plugins": [
17 18
       {

+ 11
- 0
types/antd-theme.d.ts Datei anzeigen

@@ -0,0 +1,11 @@
1
+/*This file is created with the purpose of overwrite default antd theme*/
2
+import 'antd/es/theme/interface';
3
+
4
+declare module 'antd/es/theme/interface' {
5
+  interface GlobalToken {
6
+    colorPrimary2?: string;
7
+    colorPrimary3?: string;
8
+    colorSecondary?: string;
9
+    // Add any other custom tokens here
10
+  }
11
+}

+ 8
- 0
types/knowledge.ts Datei anzeigen

@@ -0,0 +1,8 @@
1
+import type { Persona } from "@/types/persona"
2
+
3
+export interface Knowledge {
4
+    id: number;
5
+    uniq_slug: string;
6
+    name: string;
7
+    personas: Persona[];
8
+}

Laden…
Abbrechen
Speichern