Pārlūkot izejas kodu

new loading layout standard

master
azri 1 dienu atpakaļ
vecāks
revīzija
2169bb6772

+ 35
- 4
app/api/general/paymentService.ts Parādīt failu

@@ -1,6 +1,6 @@
1
-import { PaymentType } from "@/types/payment";
1
+import { PaymentType, Transaction } from "@/types/payment";
2 2
 
3
-const dummyPaymentTypeData:PaymentType[] = [
3
+const dummyPaymentTypeData: PaymentType[] = [
4 4
     {
5 5
         id: 1,
6 6
         type: "Card",
@@ -15,8 +15,39 @@ const dummyPaymentTypeData:PaymentType[] = [
15 15
     }
16 16
 ]
17 17
 
18
-export const getPaymentType = async ():Promise<PaymentType[] | undefined> => {
18
+const transactionList: Transaction[] = [
19
+    {
20
+        id: 1,
21
+        name: "Plan Payment",
22
+        created_at: "2025-07-17T10:45:23.000Z",
23
+        amount: -49.99,
24
+    },
25
+    {
26
+        id: 2,
27
+        name: "Top-up Credit",
28
+        created_at: "2025-07-16T08:15:10.000Z",
29
+        amount: 100.00,
30
+    },
31
+    {
32
+        id: 3,
33
+        name: "Refund Processed",
34
+        created_at: "2025-07-15T14:32:45.000Z",
35
+        amount: -25.00,
36
+    },
37
+    {
38
+        id: 4,
39
+        name: "Monthly Subscription",
40
+        created_at: "2025-07-01T00:00:00.000Z",
41
+        amount: 19.99,
42
+    }
43
+];
44
+
45
+export const getPaymentType = async (): Promise<PaymentType[] | undefined> => {
19 46
 
20 47
     return dummyPaymentTypeData
21
-    
48
+
49
+}
50
+
51
+export const getAllTransaction = async (): Promise<Transaction[] | undefined> => {
52
+    return transactionList
22 53
 }

+ 45
- 14
app/guest/layout.tsx Parādīt failu

@@ -1,21 +1,52 @@
1
-// app/layout.tsx
2
-import 'antd/dist/reset.css' // or 'antd/dist/antd.css' for older versions
3
-import './globals.css'
1
+'use client'
4 2
 
5
-import type { Metadata } from 'next'
6
-
7
-import { Home, MessageCircle, User } from 'lucide-react'
3
+import React from 'react'
4
+import { Layout, Flex, Row, Col } from 'antd'
8 5
 import Header from '@/components/layout/Header'
6
+import Navigation from '@/components/layout/Navigation'
7
+import { usePathname } from 'next/navigation'
9 8
 
10
-export const metadata: Metadata = {
11
-  title: 'Mobile App',
12
-  description: 'Responsive app layout with sidebar',
13
-}
14 9
 
15
-export default function RootLayout({ children }: { children: React.ReactNode }) {
10
+const AppLayout = ({ children }: { children: React.ReactNode }) => {
11
+
12
+  const pathname = usePathname()
13
+
14
+  const skipLayoutPrefixes = [
15
+    '/user/chat/',
16
+    '/user/persona/create',
17
+    '/user/knowledge/create',
18
+    '/user/settings/profile',
19
+    '/user/payment',
20
+    '/user/payment'
21
+  ];
22
+
23
+  const shouldSkipLayout = skipLayoutPrefixes.some(prefix =>
24
+    pathname.startsWith(prefix)
25
+  );
26
+
27
+  if (shouldSkipLayout) {
28
+    return <>{children}</>;
29
+  }
16 30
 
17 31
   return (
18
-    <div className="w-full max-w-[430px] min-h-screen flex flex-col justify-between relative bg-white shadow-xl">
19
-    </div>
20
-  )
32
+    <Layout className="!w-full !max-w-[430px] !mx-auto !relative !bg-white">
33
+      <Flex vertical className='!h-screen'>
34
+        <Row>
35
+          <Col span={24}>
36
+            <Header />
37
+          </Col>
38
+        </Row>
39
+
40
+        <Row className='flex-1 overflow-y-scroll overflow-x-hidden'>
41
+          <Col span={24}>{children}</Col>
42
+        </Row>
43
+
44
+        <div>
45
+          <Navigation />
46
+        </div>
47
+      </Flex>
48
+    </Layout>
49
+  );
21 50
 }
51
+
52
+export default AppLayout;

+ 10
- 0
app/guest/page.tsx Parādīt failu

@@ -0,0 +1,10 @@
1
+'use client'
2
+import { useEffect } from "react"
3
+
4
+export default function HomePage() {
5
+
6
+  return (
7
+    <div className="space-y-6">
8
+    </div>
9
+  )
10
+}

+ 22
- 0
app/not-found.tsx Parādīt failu

@@ -0,0 +1,22 @@
1
+// app/not-found.tsx
2
+"use client";
3
+
4
+import Link from "next/link";
5
+import { Button, Result } from "antd";
6
+
7
+export default function NotFound() {
8
+  return (
9
+    <div className="flex justify-center items-center min-h-screen bg-white !w-full">
10
+      <Result
11
+        status="404"
12
+        title="404"
13
+        subTitle="Sorry, the page you visited does not exist."
14
+        extra={
15
+          <Link href="/user">
16
+            <Button type="primary">Back Home</Button>
17
+          </Link>
18
+        }
19
+      />
20
+    </div>
21
+  );
22
+}

+ 5
- 8
app/user/chat/[chatID]/page.tsx Parādīt failu

@@ -1,14 +1,15 @@
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';
4
+import { Flex, Layout, Row, Col, Input, Space, Upload, Button, Result } from 'antd';
5 5
 import PageTitle from '@/components/ui/PageTitle';
6 6
 import Navigation from '@/components/layout/Navigation'
7 7
 import { useParams } from 'next/navigation'
8 8
 import { useQuery } from '@tanstack/react-query';
9 9
 import { getChat } from '@/app/api/user/chatService';
10 10
 import { PaperClipOutlined, SendOutlined } from '@ant-design/icons'
11
-
11
+import LoadingScreen from '@/components/layout/LoadingScreen';
12
+import ErrorPage from '@/components/layout/ErrorPage';
12 13
 
13 14
 const ChatPage: React.FC = () => {
14 15
 
@@ -22,13 +23,9 @@ const ChatPage: React.FC = () => {
22 23
 
23 24
     const [enabled, setEnabled] = useState(false);
24 25
 
25
-    if (isLoading) {
26
-        return <p>Loading...</p>
27
-    }
26
+    if (isLoading) return <LoadingScreen />
28 27
 
29
-    if (error) {
30
-        return <p>Loading...</p>
31
-    }
28
+    if (error) return <ErrorPage/>
32 29
 
33 30
     return (
34 31
         <Layout className="!w-full !max-w-[430px] !mx-auto !relative !bg-white">

+ 2
- 8
app/user/chat/page.tsx Parādīt failu

@@ -8,6 +8,7 @@ import { useQuery } from '@tanstack/react-query';
8 8
 import PageTitle from '@/components/ui/PageTitle';
9 9
 import type { Chat } from '@/types/chat';
10 10
 import { getAllChat } from '@/app/api/user/chatService';
11
+import LoadingScreen from '@/components/layout/LoadingScreen';
11 12
 
12 13
 const { Text } = Typography;
13 14
 
@@ -25,14 +26,7 @@ const ChatPage: React.FC = () => {
25 26
     });
26 27
 
27 28
     // 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
-    }
29
+    if (isLoading) return <LoadingScreen/>
36 30
 
37 31
     // Optional: handle unexpected empty/null data
38 32
     if (!chatList || chatList.length === 0) {

+ 1
- 1
app/user/knowledge/create/page.tsx Parādīt failu

@@ -52,7 +52,7 @@ const CreatePersona: React.FC = () => {
52 52
     return (
53 53
         <AltLayout header={<PageTitle backButton={true}>ADD KNOWLEDGE SOURCE</PageTitle>}>
54 54
             <Row>
55
-                <Col span={24} className='!px-4'>
55
+                <Col span={24} className='!p-4'>
56 56
                     <Form layout="vertical">
57 57
 
58 58
                         <Row>

+ 3
- 3
app/user/knowledge/page.tsx Parādīt failu

@@ -15,6 +15,7 @@ import PrimaryButton from '@/components/ui/PrimaryButton';
15 15
 import { Flex, Row, Col, Tag, Button, Table, Layout, Image, Typography } from 'antd';
16 16
 import type { ColumnsType } from 'antd/es/table';
17 17
 import { getAllKnowledge } from '@/app/api/user/knowledgeService';
18
+import LoadingScreen from '@/components/layout/LoadingScreen';
18 19
 
19 20
 const { Text, Title } = Typography;
20 21
 
@@ -70,9 +71,8 @@ const Knowledge: React.FC = () => {
70 71
         queryFn: () => getAllKnowledge()
71 72
     })
72 73
 
73
-    if (isLoading) {
74
-        return <p>Loading...</p>
75
-    }
74
+    // Show loading state while fetching data
75
+    if (isLoading) return <LoadingScreen/>
76 76
 
77 77
     if (!knowledges) {
78 78
         return <p></p>

+ 2
- 1
app/user/layout.tsx Parādīt failu

@@ -17,7 +17,8 @@ const AppLayout = ({ children }: { children: React.ReactNode }) => {
17 17
     '/user/knowledge/create',
18 18
     '/user/settings/profile',
19 19
     '/user/payment',
20
-    '/user/payment'
20
+    '/user/payment',
21
+    '/user/pricing'
21 22
   ];
22 23
 
23 24
   const shouldSkipLayout = skipLayoutPrefixes.some(prefix =>

+ 6
- 2
app/user/page.tsx Parādīt failu

@@ -7,10 +7,14 @@ import SectionTitle from '@/components/ui/SectionTitle';
7 7
 import PageTitle from '@/components/ui/PageTitle';
8 8
 import LoadingMeter from '@/components/ui/LoadingMeter';
9 9
 import PrimaryButton from '@/components/ui/PrimaryButton';
10
+import { useRouter } from 'next/navigation';
10 11
 
11 12
 const { Title, Text } = Typography;
12 13
 
13 14
 const page = () => {
15
+
16
+    const router = useRouter()
17
+
14 18
     return (
15 19
         <Layout className="!space-y-4 !bg-white">
16 20
             <PageTitle>DASHBOARD</PageTitle>
@@ -38,8 +42,8 @@ const page = () => {
38 42
                         </Col>
39 43
 
40 44
                         <Col span={24} className='mb-2'>
41
-                            <PrimaryButton onClick={()=>{}}>
42
-                                Upgrade Button <RightOutlined />
45
+                            <PrimaryButton onClick={()=>{ router.push("user/pricing") }}>
46
+                                Upgrade Plan <RightOutlined />
43 47
                             </PrimaryButton>
44 48
                         </Col>
45 49
 

+ 9
- 24
app/user/payment/page.tsx Parādīt failu

@@ -1,15 +1,17 @@
1 1
 "use client";
2 2
 
3
-import { useCallback, useState } from 'react';
3
+import { useState } from 'react';
4 4
 import PageTitle from '@/components/ui/PageTitle';
5 5
 import { Typography, Button, Input, Form, Select, Switch, Flex, Row, Col } from 'antd';
6 6
 import Link from 'next/link';
7
-import { BankFilled, WalletFilled } from '@ant-design/icons';
7
+
8 8
 import type { PaymentType } from '@/types/payment';
9 9
 import { useQuery } from '@tanstack/react-query';
10 10
 import { getPaymentType } from '@/app/api/general/paymentService';
11 11
 import AltLayout from '@/components/layout/AltLayout';
12 12
 import PrimaryButton from '@/components/ui/PrimaryButton';
13
+import IconCard from '@/components/ui/IconCard';
14
+import LoadingScreen from '@/components/layout/LoadingScreen';
13 15
 
14 16
 const { Text, Title } = Typography;
15 17
 
@@ -18,6 +20,7 @@ const CreatePersona: React.FC = () => {
18 20
   const [selectedPaymentID, setSelectedPaymentID] = useState<number | undefined>();
19 21
   const [companyName, setCompanyName] = useState('');
20 22
   const [cardNumber, setCardNumber] = useState('');
23
+  const [isDefault, setIsDefault] = useState(false);
21 24
   const [cvc, setCvc] = useState('');
22 25
 
23 26
   // Fetch payment types using React Query
@@ -26,13 +29,8 @@ const CreatePersona: React.FC = () => {
26 29
     queryFn: getPaymentType
27 30
   });
28 31
 
29
-  const renderIcon = useCallback((nameId: string) => {
30
-    if (nameId === "card") return <WalletFilled className='!text-white !text-xl' />
31
-    else if (nameId === "bank") return <BankFilled className='!text-white !text-xl' />
32
-    else return <></>
33
-  }, [])
34
-
35
-  if (isLoading) return <p>Loading...</p>;
32
+  // Show loading state while fetching data
33
+  if (isLoading) return <LoadingScreen/>
36 34
 
37 35
   return (
38 36
     <AltLayout header={<PageTitle backButton={true}>PAYMENT</PageTitle>}>
@@ -49,20 +47,7 @@ const CreatePersona: React.FC = () => {
49 47
 
50 48
           {/* Payment type selection */}
51 49
           <Flex wrap gap={8}>
52
-            {paymentType?.map(({ id, type, description, icon_id }) => (
53
-              <div
54
-                key={id}
55
-                className={`
56
-                  ${selectedPaymentID === id ? 'bg-blue-950' : 'bg-gray-400'} 
57
-                  hover:bg-blue-950 transition-all ease-in-out 
58
-                  rounded-lg p-4 max-w-[160px] cursor-pointer
59
-                `}
60
-                onClick={() => setSelectedPaymentID(id)}
61
-              >
62
-                {renderIcon(icon_id)}
63
-                <Text className='!text-xs !text-white !block'>Transfer via card number</Text>
64
-              </div>
65
-            ))}
50
+            {paymentType?.map(({ id, type, description, icon_id }) => <IconCard key={id} active={selectedPaymentID === id} iconName={icon_id} text={description} handleClick={() => { setSelectedPaymentID(id)}} />)}
66 51
           </Flex>
67 52
 
68 53
           {/* Payment form */}
@@ -139,7 +124,7 @@ const CreatePersona: React.FC = () => {
139 124
                     {/* Default payment switch */}
140 125
                     <Form.Item>
141 126
                       <Flex gap={4}>
142
-                        <Switch checked={true} onChange={() => { }} />
127
+                        <Switch checked={isDefault} onChange={setIsDefault} />
143 128
                         <Text className='!text-xs font-semibold'>SET AS DEFAULT</Text>
144 129
                       </Flex>
145 130
                     </Form.Item>

+ 32
- 136
app/user/payment/transaction-history/page.tsx Parādīt failu

@@ -1,162 +1,58 @@
1 1
 "use client";
2 2
 
3
-import { useCallback, useState } from 'react';
3
+import React from 'react';
4 4
 import PageTitle from '@/components/ui/PageTitle';
5
-import { Typography, Button, Input, Form, Select, Switch, Flex, Row, Col } from 'antd';
6
-import Link from 'next/link';
7
-import { BankFilled, WalletFilled } from '@ant-design/icons';
8
-import type { PaymentType } from '@/types/payment';
5
+import { Typography, Flex, Row, Col } from 'antd';
6
+import type { Transaction } from '@/types/payment';
7
+import { getAllTransaction } from '@/app/api/general/paymentService';
9 8
 import { useQuery } from '@tanstack/react-query';
10
-import { getPaymentType } from '@/app/api/general/paymentService';
9
+import PaymentCard from '@/components/ui/PaymentCard';
11 10
 import AltLayout from '@/components/layout/AltLayout';
12
-import PrimaryButton from '@/components/ui/PrimaryButton';
11
+import LoadingScreen from '@/components/layout/LoadingScreen';
13 12
 
14 13
 const { Text, Title } = Typography;
15 14
 
15
+const now = new Date();
16
+const month = now.toLocaleString('default', { month: 'long' });
17
+const year = now.getFullYear().toString();
18
+
16 19
 const CreatePersona: React.FC = () => {
17
-  // Form states
18
-  const [selectedPaymentID, setSelectedPaymentID] = useState<number | undefined>();
19
-  const [companyName, setCompanyName] = useState('');
20
-  const [cardNumber, setCardNumber] = useState('');
21
-  const [cvc, setCvc] = useState('');
22 20
 
23 21
   // Fetch payment types using React Query
24
-  const { data: paymentType, error, isLoading } = useQuery<PaymentType[] | undefined>({
25
-    queryKey: ['getPaymentType'],
26
-    queryFn: getPaymentType
22
+  const { data: transactionList, error, isLoading } = useQuery<Transaction[] | undefined>({
23
+    queryKey: ['getAllTransaction'],
24
+    queryFn: getAllTransaction
27 25
   });
28 26
 
29
-  const renderIcon = useCallback((nameId: string) => {
30
-    if (nameId === "card") return <WalletFilled className='!text-white !text-xl' />
31
-    else if (nameId === "bank") return <BankFilled className='!text-white !text-xl' />
32
-    else return <></>
33
-  }, [])
34
-
35
-  if (isLoading) return <p>Loading...</p>;
27
+  // Show loading state while fetching data
28
+  if (isLoading) return <LoadingScreen/>
36 29
 
37 30
   return (
38
-    <AltLayout header={<PageTitle backButton={true}>PAYMENT</PageTitle>}>
31
+    <AltLayout header={<PageTitle backButton={true}>TRANSACTION HISTORY</PageTitle>}>
39 32
       <Row className='!bg-white'>
40
-        <Col span={24} className='!p-4'>
41
-          {/* Section intro */}
42
-          <Flex vertical gap={10} className='!mb-8'>
43
-            <Text className='!font-semibold !text-xs !text-gray-400'>Enter your payment details <br />
44
-              <Text italic className='!text-xs'>
45
-                By continuing you agree to our <Link href="/">Terms</Link>
46
-              </Text>
47
-            </Text>
48
-          </Flex>
49
-
50
-          {/* Payment type selection */}
51
-          <Flex wrap gap={8}>
52
-            {paymentType?.map(({ id, type, description, icon_id }) => (
53
-              <div
54
-                key={id}
55
-                className={`
56
-                  ${selectedPaymentID === id ? 'bg-blue-950' : 'bg-gray-400'} 
57
-                  hover:bg-blue-950 transition-all ease-in-out 
58
-                  rounded-lg p-4 max-w-[160px] cursor-pointer
33
+        <Col span={24} className='z-10'>
34
+          <Row className='!shadow-lg'>
35
+            <Col span={24} className='!px-5 !py-3'>
36
+              <Text className='!font-bold !text-lg'>
37
+                {`Wallet Balance: RM 
38
+                  ${transactionList?.reduce((accumVal, transaction) => accumVal + transaction.amount, 0)}
59 39
                 `}
60
-                onClick={() => setSelectedPaymentID(id)}
61
-              >
62
-                {renderIcon(icon_id)}
63
-                <Text className='!text-xs !text-white !block'>Transfer via card number</Text>
64
-              </div>
65
-            ))}
66
-          </Flex>
67
-
68
-          {/* Payment form */}
69
-          <Row>
70
-            <Col span={24} className="px-2 mt-4">
71
-              <Form layout="vertical">
72
-                <Row>
73
-                  <Col span={24}>
74
-
75
-                    {/* Company Name */}
76
-                    <Form.Item label={<Text className='!italic !text-xs !font-semibold'>Company Name</Text>}>
77
-                      <Input
78
-                        placeholder="Company Name"
79
-                        value={companyName}
80
-                        onChange={(e) => setCompanyName(e.target.value)}
81
-                      />
82
-                    </Form.Item>
83
-
84
-                    {/* Card Number with formatting */}
85
-                    <Form.Item label={<Text className='!italic !text-xs !font-semibold'>Card Number</Text>}>
86
-                      <Input
87
-                        value={cardNumber}
88
-                        onChange={(e) => {
89
-                          const rawValue = e.target.value.replace(/\D/g, '').slice(0, 16);
90
-                          const formattedValue = rawValue.replace(/(.{4})/g, '$1 ').trim();
91
-                          setCardNumber(formattedValue);
92
-                        }}
93
-                      />
94
-                    </Form.Item>
95
-
96
-                    {/* Expiration Month & Year */}
97
-                    <Flex gap={10}>
98
-                      <Form.Item label={<Text className='!italic !text-xs !font-semibold'>Exp Month</Text>} className="w-full">
99
-                        <Select
100
-                          allowClear
101
-                          placeholder="Please select"
102
-                          options={Array.from({ length: 12 }, (_, i) => {
103
-                            const month = String(i + 1).padStart(2, '0');
104
-                            return { label: month, value: month };
105
-                          })}
106
-                        />
107
-                      </Form.Item>
108
-
109
-                      <Form.Item label={<Text className='!italic !text-xs !font-semibold'>Exp Year</Text>} className="w-full">
110
-                        <Select
111
-                          allowClear
112
-                          placeholder="Please select"
113
-                          options={Array.from({ length: 10 }, (_, i) => {
114
-                            const year = new Date().getFullYear() + i;
115
-                            return { label: String(year), value: String(year) };
116
-                          })}
117
-                        />
118
-                      </Form.Item>
119
-                    </Flex>
120
-
121
-                    {/* CVC with note */}
122
-                    <Form.Item label={<Text className='!italic !text-xs !font-semibold'>CVC</Text>}>
123
-                      <Flex gap={12}>
124
-                        <Input
125
-                          placeholder="123"
126
-                          className='!max-w-[50px]'
127
-                          value={cvc}
128
-                          onChange={(e) => {
129
-                            const rawValue = e.target.value.replace(/\D/g, '').slice(0, 4);
130
-                            setCvc(rawValue);
131
-                          }}
132
-                        />
133
-                        <Text className='!text-gray-300 !max-w-[160px]' style={{ fontSize: 10 }}>
134
-                          3 or 4 digits usually found on the signature strip
135
-                        </Text>
136
-                      </Flex>
137
-                    </Form.Item>
138
-
139
-                    {/* Default payment switch */}
140
-                    <Form.Item>
141
-                      <Flex gap={4}>
142
-                        <Switch checked={true} onChange={() => { }} />
143
-                        <Text className='!text-xs font-semibold'>SET AS DEFAULT</Text>
144
-                      </Flex>
145
-                    </Form.Item>
146
-                  </Col>
147
-                </Row>
148
-
149
-                {/* Submit Button */}
150
-                <PrimaryButton customStyle='!w-full !py-3'>
151
-                  CONFIRM
152
-                </PrimaryButton>
153
-              </Form>
40
+                </Text>
41
+            </Col>
42
+            <Col span={24} className='!px-5 !py-1 !bg-gray-200'>
43
+              <Text className='!font-bold !text-sm !text-gray-500'>{`${month} ${year}`}</Text>
154 44
             </Col>
155 45
           </Row>
156 46
         </Col>
47
+        <Col span={24}>
48
+          <Flex vertical>
49
+            {transactionList?.map(({ name, created_at, amount }) => <PaymentCard name={name} created_at={created_at} amount={amount} />)}
50
+          </Flex>
51
+        </Col>
157 52
       </Row>
158 53
     </AltLayout>
159 54
   );
160 55
 };
161 56
 
57
+
162 58
 export default CreatePersona;

+ 5
- 1
app/user/persona/create/page.tsx Parādīt failu

@@ -12,6 +12,7 @@ import { getPersonaStyle } from '@/app/api/user/personaService';
12 12
 import type { RcFile } from 'antd/es/upload';
13 13
 import PhoneInput from 'react-phone-input-2';
14 14
 import 'react-phone-input-2/lib/style.css';
15
+import LoadingScreen from '@/components/layout/LoadingScreen';
15 16
 
16 17
 const { Text, Title } = Typography
17 18
 
@@ -69,9 +70,12 @@ const CreatePersona: React.FC = () => {
69 70
         return false;
70 71
     };
71 72
 
73
+    // Show loading state while fetching data
74
+    if (isLoading) return <LoadingScreen/>
75
+
72 76
     return (
73 77
         <AltLayout header={<PageTitle backButton={true}>Create Persona</PageTitle>}>
74
-            <Row className='px-4'>
78
+            <Row className='!p-4'>
75 79
                 <Col span={24}>
76 80
                     <Form layout="vertical">
77 81
                         <Form.Item label={<p className='font-bold'>Persona Name</p>}>

+ 4
- 4
app/user/persona/page.tsx Parādīt failu

@@ -15,8 +15,9 @@ import PrimaryButton from '@/components/ui/PrimaryButton';
15 15
 import { Flex, Row, Col, Tag, Button, Table, Layout, Image, Typography } from 'antd';
16 16
 import type { ColumnsType } from 'antd/es/table';
17 17
 import { useRouter } from 'next/navigation';
18
+import LoadingScreen from '@/components/layout/LoadingScreen';
18 19
 
19
-const {Text, Title} = Typography;
20
+const { Text, Title } = Typography;
20 21
 
21 22
 const columns: ColumnsType<Persona> = [
22 23
     {
@@ -74,9 +75,8 @@ const PersonaPage: React.FC = () => {
74 75
         queryFn: () => getPersona()
75 76
     })
76 77
 
77
-    if (isLoading) {
78
-        return <p>Loading...</p>
79
-    }
78
+    // Show loading state while fetching data
79
+    if (isLoading) return <LoadingScreen />
80 80
 
81 81
     if (!personas) {
82 82
         return <p></p>

+ 29
- 0
app/user/pricing/dummyPlan.ts Parādīt failu

@@ -0,0 +1,29 @@
1
+export const basicPlan = [
2
+    { available: true, text: "Manual Knowledge Entry" },
3
+    { available: true, text: "Upload 5 Text Docs/month" },
4
+    { available: false, text: "Web Link Import" },
5
+    { available: false, text: "Image-to-text OCR" },
6
+    { available: false, text: "SOP Auto-Learning" },
7
+    { available: true, text: "Max 10 Knowledge Sources" },
8
+    { available: true, text: "Email Support" },
9
+];
10
+
11
+export const standardPlan = [
12
+    { available: true, text: "Manual Knowledge Entry" },
13
+    { available: true, text: "Upload 5 Text Docs/month" },
14
+    { available: true, text: "Web Link Import" },
15
+    { available: true, text: "Image-to-text OCR" },
16
+    { available: false, text: "SOP Auto-Learning" },
17
+    { available: true, text: "Max 10 Knowledge Sources" },
18
+    { available: true, text: "Email Support" },
19
+];
20
+
21
+export const premiumPlan = [
22
+    { available: true, text: "Manual Knowledge Entry" },
23
+    { available: true, text: "Upload 5 Text Docs/month" },
24
+    { available: true, text: "Web Link Import" },
25
+    { available: true, text: "Image-to-text OCR" },
26
+    { available: true, text: "SOP Auto-Learning" },
27
+    { available: true, text: "Max 10 Knowledge Sources" },
28
+    { available: true, text: "Email Support" },
29
+];

+ 22
- 204
app/user/pricing/page.tsx Parādīt failu

@@ -1,209 +1,27 @@
1 1
 "use client";
2 2
 
3
-import { useState, useEffect } from 'react'
3
+import { Row, Col } from 'antd';
4
+import PlanCard from '@/components/ui/PlanCard';
4 5
 import PageTitle from '@/components/ui/PageTitle';
5
-import { Typography, Button } from 'antd'
6
-import { CheckCircleOutlined, XOutlined } from '@ant-design/icons';
7
-
8
-const { Text, Title } = Typography;
9
-
10
-const basicPlan = [
11
-    {
12
-        available: true,
13
-        text: "Manual Knowledge Entry"
14
-    },
15
-    {
16
-        available: true,
17
-        text: "Upload 5 Text Docs/months"
18
-    },
19
-    {
20
-        available: false,
21
-        text: "No Web Link Import"
22
-    },
23
-    {
24
-        available: false,
25
-        text: "No Image-to-text OCR"
26
-    },
27
-    {
28
-        available: false,
29
-        text: "No SOP Auto-Learning"
30
-    },
31
-    {
32
-        available: true,
33
-        text: "Max 10 Knowledge Sources"
34
-    },
35
-    {
36
-        available: true,
37
-        text: "Email Support"
38
-    }
39
-]
40
-
41
-const standardPlan = [
42
-    {
43
-        available: true,
44
-        text: "Manual Knowledge Entry"
45
-    },
46
-    {
47
-        available: true,
48
-        text: "Upload 5 Text Docs/months"
49
-    },
50
-    {
51
-        available: true,
52
-        text: "No Web Link Import"
53
-    },
54
-    {
55
-        available: true,
56
-        text: "No Image-to-text OCR"
57
-    },
58
-    {
59
-        available: false,
60
-        text: "No SOP Auto-Learning"
61
-    },
62
-    {
63
-        available: true,
64
-        text: "Max 10 Knowledge Sources"
65
-    },
66
-    {
67
-        available: true,
68
-        text: "Email Support"
69
-    }
70
-]
71
-
72
-const premiumPlan = [
73
-    {
74
-        available: true,
75
-        text: "Manual Knowledge Entry"
76
-    },
77
-    {
78
-        available: true,
79
-        text: "Upload 5 Text Docs/months"
80
-    },
81
-    {
82
-        available: true,
83
-        text: "No Web Link Import"
84
-    },
85
-    {
86
-        available: true,
87
-        text: "No Image-to-text OCR"
88
-    },
89
-    {
90
-        available: true,
91
-        text: "No SOP Auto-Learning"
92
-    },
93
-    {
94
-        available: true,
95
-        text: "Max 10 Knowledge Sources"
96
-    },
97
-    {
98
-        available: true,
99
-        text: "Email Support"
100
-    }
101
-]
102
-
103
-const CreatePersona: React.FC = () => {
104
-
6
+import AltLayout from '@/components/layout/AltLayout';
7
+import { basicPlan, standardPlan, premiumPlan } from './dummyPlan';
105 8
 
9
+const Pricing: React.FC = () => {
106 10
     return (
107
-        <div className="flex flex-col bg-white">
108
-            <PageTitle title='PREMIUM PLAN' />
109
-            <div className='p-4 px-8 flex flex-col gap-5'>
110
-                <div className='border rounded-lg border-black flex flex-col overflow-hidden'>
111
-                    <div className='flex flex-col py-2 px-3'>
112
-                        <Title level={5}>Basic</Title>
113
-                        <div className='flex flex-row justify-between'>
114
-                            <div className='flex flex-row items-center gap-3'>
115
-                                <Title level={3} className='!m-0'>$5</Title>
116
-                                <div>
117
-                                    <p className='p-0 text-xs font-semibold'>Per agent</p>
118
-                                    <p className='p-0 text-xs font-semibold'>Per month</p>
119
-                                </div>
120
-                            </div>
121
-                            <div>
122
-                                <Button type='primary' className='!px-10 !font-bold'>Select</Button>
123
-                            </div>
124
-                        </div>
125
-                    </div>
126
-
127
-                    <div className='text-center border-y border-gray-200'>
128
-                        <p>ideal for simple, entry-level AI personas</p>
129
-                    </div>
130
-
131
-                    <div className='flex flex-col py-2 px-3 ps-10'>
132
-                        {basicPlan.map(({available, text}) => (
133
-                            <div>
134
-                                {(available) ? <CheckCircleOutlined className='!text-xs !align-middle !text-blue-800' /> : <XOutlined className='!text-xs !align-middle !text-red-800' />}
135
-                                <span className="ml-1 align-middle text-xs font-semibold">{text}</span>
136
-                            </div>
137
-                        ))}
138
-
139
-                    </div>
140
-                </div>
141
-                <div className='border rounded-lg border-black flex flex-col overflow-hidden'>
142
-                    <div className='flex flex-col py-2 px-3'>
143
-                        <Title level={5}>Standard</Title>
144
-                        <div className='flex flex-row justify-between'>
145
-                            <div className='flex flex-row items-center gap-3'>
146
-                                <Title level={3} className='!m-0'>$15</Title>
147
-                                <div>
148
-                                    <p className='p-0 text-xs font-semibold'>Per agent</p>
149
-                                    <p className='p-0 text-xs font-semibold'>Per month</p>
150
-                                </div>
151
-                            </div>
152
-                            <div>
153
-                                <Button type='primary' className='!px-10 !font-bold'>Select</Button>
154
-                            </div>
155
-                        </div>
156
-                    </div>
157
-
158
-                    <div className='text-center border-y border-gray-200'>
159
-                        <p>Perfect for growing AI needs</p>
160
-                    </div>
161
-
162
-                    <div className='flex flex-col py-2 px-3 ps-10'>
163
-                        {standardPlan.map(({available, text}) => (
164
-                            <div>
165
-                                {(available) ? <CheckCircleOutlined className='!text-xs !align-middle !text-blue-800' /> : <XOutlined className='!text-xs !align-middle !text-red-800' />}
166
-                                <span className="ml-1 align-middle text-xs font-semibold">{text}</span>
167
-                            </div>
168
-                        ))}
169
-
170
-                    </div>
171
-                </div>
172
-                <div className='border rounded-lg border-black flex flex-col overflow-hidden'>
173
-                    <div className='flex flex-col py-2 px-3'>
174
-                        <Title level={5}>Premium</Title>
175
-                        <div className='flex flex-row justify-between'>
176
-                            <div className='flex flex-row items-center gap-3'>
177
-                                <Title level={3} className='!m-0'>$45</Title>
178
-                                <div>
179
-                                    <p className='p-0 text-xs font-semibold'>Per agent</p>
180
-                                    <p className='p-0 text-xs font-semibold'>Per month</p>
181
-                                </div>
182
-                            </div>
183
-                            <div>
184
-                                <Button type='primary' className='!px-10 !font-bold'>Select</Button>
185
-                            </div>
186
-                        </div>
187
-                    </div>
188
-
189
-                    <div className='text-center border-y border-gray-200'>
190
-                        <p>Perfect for large enterprise AI model</p>
191
-                    </div>
192
-
193
-                    <div className='flex flex-col py-2 px-3 ps-10'>
194
-                        {premiumPlan.map(({available, text}) => (
195
-                            <div>
196
-                                {(available) ? <CheckCircleOutlined className='!text-xs !align-middle !text-blue-800' /> : <XOutlined className='!text-xs !align-middle !text-red-800' />}
197
-                                <span className="ml-1 align-middle text-xs font-semibold">{text}</span>
198
-                            </div>
199
-                        ))}
200
-
201
-                    </div>
202
-                </div>
203
-            </div>
204
-        </div>
205
-
206
-    )
207
-}
208
-
209
-export default CreatePersona
11
+        <AltLayout header={<PageTitle backButton={true}>PREMIUM PLAN</PageTitle>}>
12
+            <Row gutter={[0, 16]} className="!p-4 bg-white">
13
+                <Col xs={24} md={8}>
14
+                    <PlanCard title="Basic" price="$5" description="Ideal for simple, entry-level AI personas" features={basicPlan} />
15
+                </Col>
16
+                <Col xs={24} md={8}>
17
+                    <PlanCard title="Standard" price="$15" description="Perfect for growing AI needs" features={standardPlan} />
18
+                </Col>
19
+                <Col xs={24} md={8}>
20
+                    <PlanCard title="Premium" price="$45" description="Best for large enterprise AI models" features={premiumPlan} />
21
+                </Col>
22
+            </Row>
23
+        </AltLayout>
24
+    );
25
+};
26
+
27
+export default Pricing;

+ 1
- 2
app/user/settings/profile/page.tsx Parādīt failu

@@ -31,10 +31,9 @@ const ProfileSettings: React.FC = () => {
31 31
 
32 32
     return (
33 33
         <AltLayout header={<PageTitle backButton={true}>Profile Settings</PageTitle>}>
34
-            <Row justify="center">
34
+            <Row justify="center" className='!p-8'>
35 35
                 <Col xs={24} sm={20} md={16} lg={12}>
36 36
                     <Form layout="vertical" className="px-4 py-6 space-y-10">
37
-
38 37
                         <Row>
39 38
                             <Col span={24}>
40 39
                                 <Title level={5} className="!mb-3">Profile Photo</Title>

+ 4
- 6
app/user/wallet/page.tsx Parādīt failu

@@ -31,8 +31,7 @@ const Wallet: React.FC = () => {
31 31
 
32 32
             <Row justify="center" className="mt-6">
33 33
                 <Col xs={24} sm={20} md={16} lg={12}>
34
-                    <Form layout="vertical">
35
-
34
+                    <Form layout="vertical" className='!px-5'>
36 35
                         {/* 1. Balance + History Link */}
37 36
                         <Form.Item>
38 37
                             <Space className="w-full justify-between" align="center">
@@ -87,18 +86,17 @@ const Wallet: React.FC = () => {
87 86
                                 </Col>
88 87
                             </Row>
89 88
                         </Form.Item>
90
-
91
-                        {/* 4. Current Plan Display */}
89
+                    </Form>
90
+                    {/* 4. Current Plan Display */}
92 91
                         <Card
93 92
                             bordered
94
-                            className="border-blue-700 border-2"
93
+                            className="!border-blue-700 !rounded-none"
95 94
                             bodyStyle={{ padding: '12px' }}
96 95
                         >
97 96
                             <Text>
98 97
                                 <Text strong>Current Plan:</Text> Premium (RM25/Month)
99 98
                             </Text>
100 99
                         </Card>
101
-                    </Form>
102 100
                 </Col>
103 101
             </Row>
104 102
         </main>

+ 3
- 2
components/layout/AltLayout.tsx Parādīt failu

@@ -36,7 +36,7 @@ const AltLayout: React.FC<AltLayoutProps> = ({
36 36
                     <Col span={24}>
37 37
                         <Flex vertical className="!bg-white">
38 38
                             {/* Content wrapper - handles chat body or main content scroll */}
39
-                            <div className="flex-1 overflow-y-auto px-4 py-6 space-y-4">
39
+                            <div className="flex-1 overflow-y-auto">
40 40
                                 {children}
41 41
                             </div>
42 42
                         </Flex>
@@ -44,7 +44,8 @@ const AltLayout: React.FC<AltLayoutProps> = ({
44 44
                 </Row>
45 45
 
46 46
                 {/* Footer section - typically bottom navigation */}
47
-                <div>
47
+                {/* Whatever you do, don't change this div, I don't know why it works, but it works, trust me I already to antd this shit */}
48
+                <div> 
48 49
                     {footer}
49 50
                 </div>
50 51
 

+ 23
- 0
components/layout/ErrorPage.tsx Parādīt failu

@@ -0,0 +1,23 @@
1
+// app/not-found.tsx
2
+"use client";
3
+
4
+import { useRouter } from 'next/navigation';
5
+import { Button, Result } from "antd";
6
+
7
+export default function NotFound() {
8
+    const router = useRouter();
9
+
10
+    return (
11
+        <div className="flex justify-center items-center min-h-screen bg-white !w-full">
12
+            <Result
13
+                status="warning"
14
+                title="There are some problems with your operation."
15
+                extra={
16
+                    <Button type="primary" key="console" onClick={() => router.back()}>
17
+                        Go Back
18
+                    </Button>
19
+                }
20
+            />
21
+        </div>
22
+    );
23
+}

+ 14
- 7
components/layout/Header.tsx Parādīt failu

@@ -4,15 +4,17 @@ import { useState } from 'react'
4 4
 import { MenuOutlined } from '@ant-design/icons'
5 5
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
6 6
 import { faBars, faBell } from '@fortawesome/free-solid-svg-icons'
7
-import { Button, Space } from 'antd'
7
+import { Button, Space, Image, Flex, Typography } from 'antd'
8 8
 import Sidebar from '@/components/layout/Sidebar'
9 9
 import BellNotification from '@/components/ui/BellNotification'
10 10
 
11
+const { Text, Title } = Typography
12
+
11 13
 const Header = () => {
12 14
   const [showSideBar, setShowSideBar] = useState(false)
13 15
 
14 16
   return (
15
-    <header className="flex items-center px-4 py-2 relative z-40 bg-white shadow-sm">
17
+    <Flex align='center' className="!px-4 !py-2 !relative !z-40 !bg-white !shadow-sm">
16 18
       {/* Sidebar Trigger */}
17 19
       <Button
18 20
         type="text"
@@ -25,16 +27,21 @@ const Header = () => {
25 27
       <Sidebar open={showSideBar} setOpen={setShowSideBar} />
26 28
 
27 29
       {/* Logo + Brand */}
28
-      <div className="flex items-center ms-3">
29
-        <img src="/ruccan_logo.png" alt="Ruccan Logo" className="h-6 w-auto" />
30
-        <span className="text-black font-bold text-lg ms-3">Ruccan.com</span>
31
-      </div>
30
+      <Flex justify='center' className="!ms-3">
31
+        <Image
32
+          src="/ruccan_logo.png"
33
+          alt="Ruccan Logo"
34
+          preview={false}
35
+          height={24}
36
+        />
37
+        <Text className="!text-black !font-bold !ms-3">Ruccan.com</Text>
38
+      </Flex>
32 39
 
33 40
       {/* Notification Bell */}
34 41
       <div className="ms-auto">
35 42
         <BellNotification />
36 43
       </div>
37
-    </header>
44
+    </Flex>
38 45
   )
39 46
 }
40 47
 

+ 42
- 0
components/layout/LoadingScreen.tsx Parādīt failu

@@ -0,0 +1,42 @@
1
+"use client";
2
+
3
+import { motion } from 'framer-motion';
4
+import Image from 'next/image';
5
+
6
+const LoadingScreen = () => {
7
+    return (
8
+        <motion.div
9
+            initial={{ opacity: 0 }}
10
+            animate={{ opacity: 1 }}
11
+            exit={{ opacity: 0 }}
12
+            className="fixed inset-0 z-[999] bg-white flex flex-col items-center justify-center space-y-6"
13
+        >
14
+            <div className="relative w-32 h-32">
15
+                {/* Spinning Logo */}
16
+                <motion.div
17
+                    className="absolute top-1/2 left-1/2 w-16 h-16 -translate-x-1/2 -translate-y-1/2"
18
+                    animate={{ rotate: 360 }}
19
+                    transition={{
20
+                        repeat: Infinity,
21
+                        duration: 2,
22
+                        ease: "linear",
23
+                    }}
24
+                >
25
+                    <Image
26
+                        src="/ruccan_logo2.png"
27
+                        alt="Ruccan Logo"
28
+                        fill
29
+                        className="object-contain"
30
+                    />
31
+                </motion.div>
32
+            </div>
33
+
34
+            {/* Loading Text */}
35
+            <p className="text-gray-600 text-sm tracking-wide animate-pulse">
36
+                Loading, please wait...
37
+            </p>
38
+        </motion.div>
39
+    );
40
+};
41
+
42
+export default LoadingScreen;

+ 1
- 1
components/layout/Navigation.tsx Parādīt failu

@@ -9,7 +9,7 @@ import { useRouter, usePathname } from 'next/navigation'
9 9
 
10 10
 const navItems = [
11 11
     {
12
-        key: '/user/',
12
+        key: '/user',
13 13
         icon: <HomeOutlined />,
14 14
         label: 'Home',
15 15
     },

+ 4
- 4
components/layout/Sidebar.tsx Parādīt failu

@@ -128,17 +128,17 @@ const Sidebar = ({ open = false, setOpen = () => { } }: SidebarProps) => {
128 128
                 label: 'Settings',
129 129
                 children: [
130 130
                   {
131
-                    key: '/settings/general',
131
+                    key: 'user/settings/general',
132 132
                     icon: <FontAwesomeIcon icon={faCog} />,
133 133
                     label: 'General',
134 134
                   },
135 135
                   {
136
-                    key: '/settings/profile',
136
+                    key: 'user/settings/profile',
137 137
                     icon: <FontAwesomeIcon icon={faUserAlt} />,
138 138
                     label: 'Profile',
139 139
                   },
140 140
                   {
141
-                    key: '/settings/chat-preference',
141
+                    key: 'user/settings/chat-preference',
142 142
                     icon: <FontAwesomeIcon icon={faComment} />,
143 143
                     label: 'Chat Preference',
144 144
                   }
@@ -161,7 +161,7 @@ const Sidebar = ({ open = false, setOpen = () => { } }: SidebarProps) => {
161 161
             label: 'Help',
162 162
           },
163 163
           {
164
-            key: '/logout',
164
+            key: '/guest',
165 165
             icon: <FontAwesomeIcon icon={faSignOut} className='!text-red-600' />,
166 166
             label: <p className='text-red-400 font-bold'>Logout</p>,
167 167
           },

+ 31
- 0
components/ui/IconCard.tsx Parādīt failu

@@ -0,0 +1,31 @@
1
+import React, { useCallback } from 'react'
2
+import { Typography } from 'antd';
3
+import { BankFilled, WalletFilled } from '@ant-design/icons';
4
+
5
+const { Text, Title } = Typography
6
+
7
+const IconCard = ({ active, iconName, text, handleClick }) => {
8
+
9
+    const renderIcon = useCallback((nameId: string) => {
10
+        if (nameId === "card") return <WalletFilled className='!text-white !text-xl' />
11
+        else if (nameId === "bank") return <BankFilled className='!text-white !text-xl' />
12
+        else return <></>
13
+    }, [])
14
+
15
+
16
+    return (
17
+        <div
18
+            onClick={handleClick}
19
+            className={`
20
+                  ${active ? 'bg-blue-950' : 'bg-gray-400'} 
21
+                  hover:bg-blue-950 transition-all ease-in-out 
22
+                  rounded-lg p-4 max-w-[160px] cursor-pointer
23
+                `}
24
+        >
25
+            {renderIcon(iconName)}
26
+            <Text className='!text-xs !text-white !block'>{text}</Text>
27
+        </div>
28
+    )
29
+}
30
+
31
+export default IconCard

+ 1
- 1
components/ui/PageTitle.tsx Parādīt failu

@@ -38,7 +38,7 @@ const PageTitle: React.FC<PageTitleProps> = ({
38 38
                     icon={<FontAwesomeIcon icon={faArrowLeft} />}
39 39
                     onClick={() => router.back()}
40 40
                     style={{ fontSize: 20 }}
41
-                    className="!text-white"
41
+                className="!text-white"
42 42
                 />
43 43
             ) : (
44 44
                 <div style={{ width: 40 }} /> // Placeholder to keep spacing

+ 29
- 0
components/ui/PaymentCard.tsx Parādīt failu

@@ -0,0 +1,29 @@
1
+import React, { useState } from 'react';
2
+import { Typography, Flex} from 'antd';
3
+import type { Transaction } from '@/types/payment';
4
+import formatDate from '@/helper/util/formatDateTime';
5
+
6
+const { Text, Title } = Typography;
7
+
8
+type PaymentCardProps = Omit<Transaction, 'id'>
9
+
10
+const PaymentCard: React.FC<PaymentCardProps> = ({ name, created_at, amount }: PaymentCardProps) => {
11
+
12
+  const sign = (amount > 0) ? "+" : "-";
13
+
14
+  return (
15
+    <Flex className='border-y !bg-white hover:!bg-gray-100 cursor-pointer !border-gray-100 !py-2 !px-6'>
16
+      <Flex vertical flex={1}>
17
+        <Text className='!font-bold'>{name}</Text>
18
+        <Text>{formatDate(created_at)}</Text>
19
+      </Flex>
20
+      <Flex align='center'>
21
+        <Text className={`!font-bold ${(amount > 0) ? '!text-green-700' : '!text-red-700'} `}>
22
+          {`${sign} RM ${Math.abs(amount).toFixed(2)}`}
23
+        </Text>
24
+      </Flex>
25
+    </Flex>
26
+  )
27
+}
28
+
29
+export default PaymentCard;

+ 61
- 0
components/ui/PlanCard.tsx Parādīt failu

@@ -0,0 +1,61 @@
1
+import { Typography, Button, Row, Col, Card, Flex } from 'antd';
2
+import { CheckCircleOutlined, CloseCircleOutlined } from '@ant-design/icons';
3
+import PrimaryButton from './PrimaryButton';
4
+
5
+const { Title, Text } = Typography;
6
+
7
+type Feature = {
8
+    available: boolean;
9
+    text: string;
10
+};
11
+
12
+type PlanCardProps = {
13
+    title: string;
14
+    price: string;
15
+    description: string;
16
+    features: Feature[];
17
+};
18
+
19
+const PlanCard: React.FC<PlanCardProps> = ({ title, price, description, features }) => {
20
+    return (
21
+        <Card
22
+            bordered
23
+            className="!w-full !h-full"
24
+            bodyStyle={{ padding: 0 }}
25
+            headStyle={{border:"none"}}
26
+            title={<Title level={4} className="!mb-0 !text-blue-600">{title}</Title>}
27
+        >
28
+            <Flex justify="space-between" align="center" className="!p-4">
29
+                <Flex gap={8}>
30
+                    <Title level={3} className="!m-0">{price}</Title>
31
+                    <div>
32
+                        <Text className="!text-xs !font-semibold block">Per agent</Text>
33
+                        <Text className="!text-xs !font-semibold block">Per month</Text>
34
+                    </div>
35
+                </Flex>
36
+                <Flex align='center'>
37
+                    <PrimaryButton customStyle="!font-bold !px-10 !rounded-md">Select</PrimaryButton>
38
+                </Flex>
39
+            </Flex>
40
+
41
+            <div className="text-center border-y py-1 mb-2 !border-gray-300">
42
+                <Text className="!text-xs">{description}</Text>
43
+            </div>
44
+
45
+            <Flex vertical gap={8} className="!px-8 !py-4">
46
+                {features.map((feature, idx) => (
47
+                    <Flex key={idx} align="center" gap={6}>
48
+                        {feature.available ? (
49
+                            <CheckCircleOutlined className="!text-blue-600 text-xs" />
50
+                        ) : (
51
+                            <CloseCircleOutlined className="!text-red-500 text-xs" />
52
+                        )}
53
+                        <Text className="!text-xs !font-semibold">{feature.text}</Text>
54
+                    </Flex>
55
+                ))}
56
+            </Flex>
57
+        </Card>
58
+    );
59
+};
60
+
61
+export default PlanCard;

+ 0
- 2
components/ui/PrimaryButton.tsx Parādīt failu

@@ -21,8 +21,6 @@ const PrimaryButton: React.FC<PrimaryButtonProps> = ({
21 21
 
22 22
     const token = useThemeToken();
23 23
 
24
-    console.log(token.colorPrimary2); 
25
-
26 24
     const baseClasses = 'mt-2 !rounded-full !px-6 !py-4';
27 25
     const filledClasses = '!border-0 !bg-[#602FD0] !text-[#fff] hover:!bg-[#271550]';
28 26
     const outlineClasses = '!border !bg-[#fff] !text-[#602FD0] hover:!bg-[#602FD0] hover:!text-[#fff]';

+ 18
- 0
helper/util/formatDateTime.ts Parādīt failu

@@ -0,0 +1,18 @@
1
+const formatDate = (isoDate: string) => {
2
+  const date = new Date(isoDate);
3
+
4
+  const day = date.getDate().toString().padStart(2, '0');
5
+  const month = date.toLocaleString('default', { month: 'short' }); // e.g., "May"
6
+  const year = date.getFullYear();
7
+
8
+  let hours = date.getHours();
9
+  const minutes = date.getMinutes().toString().padStart(2, '0');
10
+  const ampm = hours >= 12 ? 'PM' : 'AM';
11
+
12
+  hours = hours % 12;
13
+  hours = hours ? hours : 12; // the hour '0' should be '12'
14
+
15
+  return `${day} ${month} ${year}, ${hours}:${minutes} ${ampm}`;
16
+};
17
+
18
+export default formatDate;

+ 43
- 0
package-lock.json Parādīt failu

@@ -17,6 +17,7 @@
17 17
         "@tanstack/react-query": "^5.81.5",
18 18
         "antd": "^5.26.3",
19 19
         "classnames": "^2.5.1",
20
+        "framer-motion": "^12.23.6",
20 21
         "lucide-react": "^0.525.0",
21 22
         "next": "15.3.4",
22 23
         "next-auth": "^4.24.11",
@@ -3985,6 +3986,33 @@
3985 3986
         "url": "https://github.com/sponsors/rawify"
3986 3987
       }
3987 3988
     },
3989
+    "node_modules/framer-motion": {
3990
+      "version": "12.23.6",
3991
+      "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.6.tgz",
3992
+      "integrity": "sha512-dsJ389QImVE3lQvM8Mnk99/j8tiZDM/7706PCqvkQ8sSCnpmWxsgX+g0lj7r5OBVL0U36pIecCTBoIWcM2RuKw==",
3993
+      "license": "MIT",
3994
+      "dependencies": {
3995
+        "motion-dom": "^12.23.6",
3996
+        "motion-utils": "^12.23.6",
3997
+        "tslib": "^2.4.0"
3998
+      },
3999
+      "peerDependencies": {
4000
+        "@emotion/is-prop-valid": "*",
4001
+        "react": "^18.0.0 || ^19.0.0",
4002
+        "react-dom": "^18.0.0 || ^19.0.0"
4003
+      },
4004
+      "peerDependenciesMeta": {
4005
+        "@emotion/is-prop-valid": {
4006
+          "optional": true
4007
+        },
4008
+        "react": {
4009
+          "optional": true
4010
+        },
4011
+        "react-dom": {
4012
+          "optional": true
4013
+        }
4014
+      }
4015
+    },
3988 4016
     "node_modules/function-bind": {
3989 4017
       "version": "1.1.2",
3990 4018
       "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -5324,6 +5352,21 @@
5324 5352
         "url": "https://github.com/sponsors/isaacs"
5325 5353
       }
5326 5354
     },
5355
+    "node_modules/motion-dom": {
5356
+      "version": "12.23.6",
5357
+      "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.6.tgz",
5358
+      "integrity": "sha512-G2w6Nw7ZOVSzcQmsdLc0doMe64O/Sbuc2bVAbgMz6oP/6/pRStKRiVRV4bQfHp5AHYAKEGhEdVHTM+R3FDgi5w==",
5359
+      "license": "MIT",
5360
+      "dependencies": {
5361
+        "motion-utils": "^12.23.6"
5362
+      }
5363
+    },
5364
+    "node_modules/motion-utils": {
5365
+      "version": "12.23.6",
5366
+      "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz",
5367
+      "integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==",
5368
+      "license": "MIT"
5369
+    },
5327 5370
     "node_modules/ms": {
5328 5371
       "version": "2.1.3",
5329 5372
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",

+ 1
- 0
package.json Parādīt failu

@@ -18,6 +18,7 @@
18 18
     "@tanstack/react-query": "^5.81.5",
19 19
     "antd": "^5.26.3",
20 20
     "classnames": "^2.5.1",
21
+    "framer-motion": "^12.23.6",
21 22
     "lucide-react": "^0.525.0",
22 23
     "next": "15.3.4",
23 24
     "next-auth": "^4.24.11",

+ 7
- 0
types/payment.ts Parādīt failu

@@ -3,4 +3,11 @@ export interface PaymentType {
3 3
     type: string,
4 4
     description: string,
5 5
     icon_id: string
6
+}
7
+
8
+export type Transaction = {
9
+    id: number;
10
+    name: string;
11
+    created_at: string;
12
+    amount: number;
6 13
 }

Notiek ielāde…
Atcelt
Saglabāt