diff --git a/src/assets/styles/components/_chart.css b/src/assets/styles/components/_chart.css new file mode 100644 index 0000000..d67191d --- /dev/null +++ b/src/assets/styles/components/_chart.css @@ -0,0 +1 @@ +.apexcharts-canvas { margin-left: 0 !important; margin-right: 0 !important; } diff --git a/src/assets/styles/components/index.css b/src/assets/styles/components/index.css index 9383980..a69f033 100644 --- a/src/assets/styles/components/index.css +++ b/src/assets/styles/components/index.css @@ -1,4 +1,5 @@ @import "./_alert.css"; +@import "./_chart.css"; @import "./_avatar.css"; @import "./_badge.css"; @import "./_button.css"; diff --git a/src/components/collabberry/custom-components/CustomRainbowKit/UserDisconnect.tsx b/src/components/collabberry/custom-components/CustomRainbowKit/UserDisconnect.tsx new file mode 100644 index 0000000..f44d411 --- /dev/null +++ b/src/components/collabberry/custom-components/CustomRainbowKit/UserDisconnect.tsx @@ -0,0 +1,28 @@ +import Button from "@/components/ui/Button"; +import useAuth from "@/utils/hooks/useAuth"; +import { useAccount } from "wagmi"; + +export const DisconnectButton = () => { + const { signOut } = useAuth(); + const { isConnected } = useAccount(); + + + if (!isConnected) { + return null; + } + + const handleDisconnect = async () => { + try { + await signOut(); + } catch (error) { + console.error("Error disconnecting:", error); + } + }; + + return ( + + ); +}; \ No newline at end of file diff --git a/src/components/collabberry/custom-components/MessageSquare.tsx b/src/components/collabberry/custom-components/MessageSquare.tsx new file mode 100644 index 0000000..f8f4a76 --- /dev/null +++ b/src/components/collabberry/custom-components/MessageSquare.tsx @@ -0,0 +1,29 @@ +import React from 'react'; + +interface MessageSquareProps extends React.SVGProps { + size?: number | string; + className?: string; +} +const MessageSquare: React.FC = ({ + size = 24, + className = "w-6 h-6", + ...props +}) => ( + + + +); + +export default MessageSquare; \ No newline at end of file diff --git a/src/components/layouts/AuthLayout/AuthLayout.tsx b/src/components/layouts/AuthLayout/AuthLayout.tsx index 4403ca3..1a365d6 100644 --- a/src/components/layouts/AuthLayout/AuthLayout.tsx +++ b/src/components/layouts/AuthLayout/AuthLayout.tsx @@ -7,6 +7,15 @@ import { LAYOUT_TYPE_BLANK } from "@/constants/theme.constant"; import Header from "@/components/template/Header"; import CollabberyLogoFull from "@/assets/svg/CollabberryLogoFull"; import { SvgIcon } from "@/components/shared"; +import { DisconnectButton } from "@/components/collabberry/custom-components/CustomRainbowKit/UserDisconnect"; + +const AuthHeaderActionEnd = () => { + return ( + <> + + + ); +}; const AuthLayout = () => { const layoutType = useAppSelector((state) => state.theme.layout.type); @@ -16,6 +25,7 @@ const AuthLayout = () => {
} + headerEnd={} />
diff --git a/src/components/layouts/ModernLayout.tsx b/src/components/layouts/ModernLayout.tsx index d1cecf8..e0c2de1 100644 --- a/src/components/layouts/ModernLayout.tsx +++ b/src/components/layouts/ModernLayout.tsx @@ -20,7 +20,6 @@ const HeaderActionsStart = () => { const HeaderActionsEnd = () => { return ( <> - ); diff --git a/src/components/ui/Button/Button.tsx b/src/components/ui/Button/Button.tsx index 03b5f0a..81d4b0f 100644 --- a/src/components/ui/Button/Button.tsx +++ b/src/components/ui/Button/Button.tsx @@ -11,7 +11,7 @@ import type { ReactNode, ComponentPropsWithRef, MouseEvent } from 'react' export interface ButtonProps extends CommonProps, - Omit, 'onClick'> { + Omit, 'onClick'> { active?: boolean block?: boolean color?: string @@ -21,7 +21,7 @@ export interface ButtonProps onClick?: (e: MouseEvent) => void shape?: TypeAttributes.Shape size?: TypeAttributes.Size - variant?: 'solid' | 'twoTone' | 'plain' | 'default' + variant?: 'solid' | 'twoTone' | 'plain' | 'default' | 'transparent' } type ButtonColor = { @@ -155,15 +155,30 @@ const Button = forwardRef((props, ref) => { return getBtnColor(btn) } + const transparentColor = () => { + const btn = { + bgColor: active + ? `bg-gray-200 dark:bg-gray-700` + : "bg-transparent border border-transparent", + + textColor: `text-gray-300 dark:text-gray-100`, + hoverColor: active + ? "" + : `hover:bg-gray-100/20 dark:hover:bg-gray-700/20`, + + activeColor: `active:bg-gray-100/40 dark:active:bg-gray-700/40`, + }; return getBtnColor(btn); + }; + + const getBtnColor = ({ bgColor, hoverColor, activeColor, textColor, }: ButtonColor) => { - return `${bgColor} ${ - disabled || loading ? disabledClass : hoverColor + ' ' + activeColor - } ${textColor}` + return `${bgColor} ${disabled || loading ? disabledClass : hoverColor + ' ' + activeColor + } ${textColor}` } const btnColor = () => { @@ -174,6 +189,8 @@ const Button = forwardRef((props, ref) => { return twoToneColor() case 'plain': return plainColor() + case 'transparent': + return transparentColor() case 'default': return defaultColor() default: diff --git a/src/configs/navigation.config/index.ts b/src/configs/navigation.config/index.ts index 6befdc8..25a788d 100644 --- a/src/configs/navigation.config/index.ts +++ b/src/configs/navigation.config/index.ts @@ -25,6 +25,26 @@ const navigationConfig: NavigationTree[] = [ authority: [], subMenu: [], }, + // { + // key: "admin.assistant", + // path: "/assistant", + // title: "Agreement Assistant", + // translateKey: "nav.admin.assistant", + // icon: "home", + // type: NAV_ITEM_TYPE_ITEM, + // authority: [], + // subMenu: [], + // }, + // { + // key: "admin.assistant", + // path: "/assistant", + // title: "Agreement Assistant", + // translateKey: "nav.admin.assistant", + // icon: "home", + // type: NAV_ITEM_TYPE_ITEM, + // authority: [], + // subMenu: [], + // }, { key: "admin.assessment", path: "/assessment", diff --git a/src/configs/routes.config/routes.config.ts b/src/configs/routes.config/routes.config.ts index b9e6fad..7a47568 100644 --- a/src/configs/routes.config/routes.config.ts +++ b/src/configs/routes.config/routes.config.ts @@ -34,6 +34,12 @@ export const protectedRoutes = [ component: lazy(() => import("@/views/main/Scores/Scores")), authority: [], }, + // { + // key: "admin.assistant", + // path: "/assistant", + // component: lazy(() => import("@/views/main/AgreementAssistant/AgreementAssistant")), + // authority: [], + // }, { key: "admin.my-scores", path: "/scores/my-scores", diff --git a/src/constants/chart.constant.ts b/src/constants/chart.constant.ts index d61ac6d..7b8a0fe 100644 --- a/src/constants/chart.constant.ts +++ b/src/constants/chart.constant.ts @@ -37,3 +37,26 @@ export const COLORS_LIGHT = [ COLOR_6_LIGHT, COLOR_7_LIGHT, ] + +export const CUSTOM_COLORS = [ + "#6B7AC7", // Blue Yonder + "#B14D57", // Rose Taupe + "#4A90E2", // Dodger Blue + "#50C878", // Emerald + "#FFA726", // Carrot Orange + "#FDD835", // Dandelion + "#EC407A", // Cerise + "#9575CD", // Amethyst + "#26A69A", // Teal + "#FF7043", // Coral + "#BA68C8", // Wisteria + "#283593", // Dark Blue + "#4DD0E1", // Turquoise + "#AEEA00", // Lime + "#D81B60", // Raspberry + "#00ACC1", // Cyan + "#FFD54F", // Maize + "#5E35B1", // Violet + "#9E9D24", // Olive + "#90A4AE", // Slate Gray + ] \ No newline at end of file diff --git a/src/views/main/AgreementAssistant/AgreementAssistant.tsx b/src/views/main/AgreementAssistant/AgreementAssistant.tsx new file mode 100644 index 0000000..a1ca7ee --- /dev/null +++ b/src/views/main/AgreementAssistant/AgreementAssistant.tsx @@ -0,0 +1,622 @@ +import { Alert, Badge, Button, Card, FormContainer, FormItem, Input } from '@/components/ui'; +import { useHandleError } from '@/services/HandleError'; +import { Form, useFormik } from 'formik'; +import React, { useState } from 'react'; +import * as Yup from "yup"; +import { useNavigate } from 'react-router-dom'; +import { useMutation } from '@tanstack/react-query'; +import { handleErrorMessage, handleSuccess } from '@/components/collabberry/helpers/ToastNotifications'; +import MessageSquare from '@/components/collabberry/custom-components/MessageSquare'; +import { Loading } from '@/components/shared'; + +// const AgreementAssistant: React.FC = () => { + +// const navigate = useNavigate(); +// const handleError = useHandleError() +// const [conversationStarted, setConversationStarted] = useState(false); +// const [userInput, setUserInput] = useState(""); +// const [conversationHistory, setConversationHistory] = useState< +// Array<{ +// type: "user" | "assistant"; +// content: string; +// }> +// >([]); +// const [showForm, setShowForm] = useState(false); +// const [summaryPresented, setSummaryPresented] = useState(false); +// const [finalizeSummary, setFinalizeSummary] = useState(false); +// const [suggestions, setSuggestions] = useState<{ +// role?: string; +// experience?: string; +// responsibilities?: string[]; +// marketRate?: number; +// location?: string; +// }>({}); + +// const analyzeRoleMutation = useMutation({ +// mutationFn: async (context: { +// status: string; +// currentMessage: string; +// history: Array<{ type: "user" | "assistant"; content: string }>; +// }) => { +// console.log("Sending analysis request:", context); +// // Simulate an API response for demonstration purposes +// return { +// analysis: "This is a simulated analysis response.", +// suggestedRole: "Developer", +// suggestedExperience: "3+ years", +// suggestedResponsibilities: ["Coding", "Testing"], +// marketRate: 5000, +// suggestedLocation: "Remote", +// }; +// }, +// }); + +// const createAgreementMutation = useMutation({ +// mutationFn: async (data: any) => { +// // const res = await apiRequest("POST", "http://localhost:3001/api/agreements", data); +// // return res.json(); +// }, +// onSuccess: (data) => { +// // Invalidate the agreements query cache so the list refreshes when user goes back +// // queryClient.invalidateQueries({ queryKey: ["http://localhost:3001/api/agreements"] }); + +// handleSuccess({ +// title: "Agreement created", +// description: "Your agreement has been saved successfully.", +// }); +// // navigate(`/agreements/${data?.id}`); +// }, +// }); + +// const handleStartConversation = async () => { +// console.log("Starting conversation..."); +// setConversationStarted(true); +// setConversationHistory([ +// { +// type: "assistant", +// content: +// "Hi! I'd love to understand your interest. Why do you want to join the team?", +// }, +// ]); +// }; + +// const handleSubmitMessage = async () => { +// if (!userInput.trim()) return; + +// // Add user message to history +// const updatedHistory = [ +// ...conversationHistory, +// { type: "user" as const, content: userInput }, +// ]; +// setConversationHistory(updatedHistory); + +// try { +// console.log("Analyzing message:", userInput); +// // Get AI analysis with full conversation context +// const analysis = await analyzeRoleMutation.mutateAsync({ +// status: "PENDING", +// currentMessage: userInput, +// history: updatedHistory, +// }); + +// console.log("Analysis response:", analysis); + +// if (analysis?.analysis === "" && summaryPresented) { +// if (analysis.analysis === "" && summaryPresented) { +// // User has confirmed the summary, prepare to show the final form + +// // Update suggestions state with final values from the AI +// const updatedSuggestions = { +// role: analysis.suggestedRole || suggestions.role, +// experience: analysis.suggestedExperience || suggestions.experience, +// responsibilities: +// analysis.suggestedResponsibilities || suggestions.responsibilities, +// marketRate: analysis.marketRate || suggestions.marketRate, +// location: analysis.suggestedLocation || suggestions.location, +// }; +// setSuggestions(updatedSuggestions); + +// // Set all form values, including hidden fields that will be submitted with the form +// // formik.resetForm({ +// // role: updatedSuggestions.role, +// // experience: updatedSuggestions.experience, +// // responsibilities: updatedSuggestions.responsibilities || [], +// // marketRate: updatedSuggestions.marketRate || 0, +// // industry: "web3", // Default industry +// // commitmentLevel: 40, // Default 40 hours +// // fiatAmount: 0, // Default 0, user will update +// // username: "", // User will fill this out +// // name: "", // Will be set from username during submission +// // location: updatedSuggestions.location || "Remote", // Default to Remote if not provided +// // }); + +// // Log the state for debugging +// console.log("Setting form values:", { +// role: updatedSuggestions.role, +// experience: updatedSuggestions.experience, +// marketRate: updatedSuggestions.marketRate, +// responsibilities: updatedSuggestions.responsibilities, +// location: updatedSuggestions.location, +// }); + +// // Show form fields to the user +// setFinalizeSummary(true); +// setShowForm(true); + +// // Force validation to run +// // setTimeout(() => { +// // formik.trigger(); +// // }, 100); + +// return; +// } + +// // Add AI response to history +// setConversationHistory((prev) => [ +// ...prev, +// { +// type: "assistant" as const, +// content: analysis.analysis, +// }, +// ]); + +// // Update suggestions if provided +// if ( +// analysis.suggestedRole || +// analysis.suggestedExperience || +// analysis.suggestedResponsibilities || +// analysis.marketRate || +// analysis.suggestedLocation +// ) { +// setSuggestions((prev) => ({ +// ...prev, +// role: analysis.suggestedRole || prev.role, +// experience: analysis.suggestedExperience || prev.experience, +// responsibilities: +// analysis.suggestedResponsibilities || prev.responsibilities, +// marketRate: analysis.marketRate || prev.marketRate, +// location: analysis.suggestedLocation || prev.location, +// })); + +// // If this is the first time we have all the information, mark as summary presented +// if ( +// analysis.suggestedRole && +// analysis.suggestedExperience && +// analysis.suggestedResponsibilities && +// analysis.marketRate && +// analysis.suggestedLocation && +// !summaryPresented +// ) { +// setSummaryPresented(true); +// } +// } +// } +// } catch (error) { +// console.error("Error analyzing message:", error); +// handleError({ +// title: "Error", +// description: "Failed to analyze your message. Please try again.", +// variant: "destructive", +// }); +// } + +// setUserInput(""); +// }; + +// const formik = useFormik({ +// initialValues: { +// commitmentLevel: 0 as number, +// fiatAmount: 0 as number, +// username: "" as string, +// }, +// validationSchema: Yup.object({ +// commitmentLevel: Yup.number() +// .required("Required") +// .min(1, "Must be at least 1") +// .max(40, "Can’t exceed 40"), +// fiatAmount: Yup.number() +// .required("Required") +// .min(1, "Must be at least $1"), +// username: Yup.string().required("Please enter your name"), +// }), +// onSubmit: (values) => { +// // enrich with suggestions & defaults +// const payload = { +// ...values, +// name: values.username, +// role: suggestions.role, +// experience: suggestions.experience, +// responsibilities: +// suggestions.responsibilities && suggestions.responsibilities.length +// ? suggestions.responsibilities +// : [], +// marketRate: suggestions.marketRate, +// industry: "web3", +// location: suggestions.location || "Remote", +// }; + +// createAgreementMutation.mutate(payload); +// }, +// }); + +// return ( +//
+//
+// +//
+//

Join Collabberry

+// +// {/* */} +// AI Assisted +// +//
+//
+// {!conversationStarted ? ( +//
+// +// {/* */} +//
Let's Talk About Your Contribution
+//
+// I'd love to learn about your experience and how you'd like +// to help build Collabberry. Let's have a conversation about +// your interests and find the perfect role for you within the +// team. +//
+//
+// +//
+// ) : ( +//
+// {/* Conversation History */} +//
+// {conversationHistory.map((message, index) => ( +//
+// {message.type === "assistant" && ( +// // +//
+// )} +//
"), +// }} +// /> +//
+// ))} +//
+ +// {summaryPresented && !finalizeSummary && ( +//
+// +// +//
+// )} + +// {/* Input Area */} +// {(!summaryPresented || +// (summaryPresented && !finalizeSummary)) && ( +//
+//