Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26,607 changes: 0 additions & 26,607 deletions package-lock.json

This file was deleted.

8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"format:fix": "prettier --write --ignore-path .gitignore ."
},
"dependencies": {
"3d-force-graph-vr": "^2.2.2",
"@amplitude/analytics-browser": "^1.9.4",
"@date-io/date-fns": "^2.17.0",
"@emotion/react": "^11.10.5",
Expand All @@ -36,7 +37,6 @@
"@types/node": "18.11.9",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"3d-force-graph-vr": "^2.2.2",
"aframe": "^1.4.2",
"axios": "^1.2.2",
"clsx": "^1.2.1",
Expand All @@ -48,6 +48,7 @@
"eslint-plugin-react": "^7.32.0",
"highcharts": "^11.4.1",
"highcharts-react-official": "^3.2.1",
"jotai": "^2.12.3",
"jwt-decode": "^3.1.2",
"lodash": "^4.17.21",
"moment": "^2.29.4",
Expand All @@ -69,7 +70,7 @@
"react-toastify": "^9.1.1",
"sharp": "^0.33.5",
"timezones-list": "^3.0.1",
"typescript": "^5.6.3",
"typescript": "^5.6.0",
"viem": "^2.21.40",
"wagmi": "^2.12.25",
"zustand": "^4.3.1"
Expand All @@ -86,6 +87,7 @@
"@typescript-eslint/parser": "^7.0.2",
"autoprefixer": "^10.4.13",
"babel-jest": "^29.5.0",
"daisyui": "4.12.24",
"eslint": "^8.56.0",
"eslint-plugin-simple-import-sort": "^12.0.0",
"eslint-plugin-unused-imports": "^3.1.0",
Expand All @@ -98,4 +100,4 @@
"react-test-renderer": "^18.2.0",
"tailwindcss": "^3.2.2"
}
}
}
14,769 changes: 14,769 additions & 0 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

16 changes: 6 additions & 10 deletions src/pages/agent/index.tsx → src/app/dashboard/agent/page.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { FaRobot } from "react-icons/fa";
'use client';

import { FaRobot } from "react-icons/fa6";

import { Chat } from "@/components/chat/Chat";
import SEO from "@/components/global/SEO";

import { defaultLayout } from "@/layouts/defaultLayout";

function Index() {
export default function AgentPage() {
return (
<>
<SEO titleTemplate="Agent" />
<div className="flex flex-col gap-4 bg-gray-100 w-full h-screen p-8">
<div className="flex flex-col gap-4 bg-gray-100 w-full p-8 overflow-y-auto">
<div className="flex flex-col gap-4">
<h1 className="text-3xl font-bold text-center flex items-center justify-center gap-2">
<FaRobot className="inline-block mr-2" />
Expand All @@ -21,8 +21,4 @@ function Index() {
</div >
</>
);
}

Index.pageLayout = defaultLayout;

export default Index;
}
62 changes: 62 additions & 0 deletions src/app/dashboard/community-guardian/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
'use client'

import { useRouter } from "next/navigation"
import { FaDiscord, FaTelegram } from "react-icons/fa"
import { FaXTwitter } from "react-icons/fa6"

import { MODULE_CONTEXT } from "@/constants/ModuleContext"

export default function CommunityGuardianPage() {
const router = useRouter()

// TODO: rename to community-guardian
const data = MODULE_CONTEXT.find(module => module.name === 'violationDetection')
Comment on lines +12 to +13
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Address the TODO comment about renaming the module

There's a pending TODO about renaming 'violationDetection' to 'community-guardian'. This should be addressed to maintain consistency between the module name and URL path.


🏁 Script executed:

#!/bin/bash
# Check if the module name is referenced elsewhere in the codebase
rg -i "violationDetection|community-guardian" --type ts --type tsx

Length of output: 96


🏁 Script executed:

#!/usr/bin/env bash
# Search for references to 'violationDetection' and 'community-guardian' in all TS/TSX files
rg -in 'violationDetection|community-guardian' --glob '*.ts' --glob '*.tsx'

Length of output: 1577


Rename the dashboard module key to ‘community-guardian’

Please update the module name in both the context definition and the page lookup to match the URL path, and remove the outstanding TODO:

  • src/constants/ModuleContext.ts (around line 61)
    - name: "violationDetection",
    + name: "community-guardian",
  • src/app/dashboard/community-guardian/page.tsx (lines 12–13)
    - // TODO: rename to community-guardian
    - const data = MODULE_CONTEXT.find(module => module.name === 'violationDetection')
    + const data = MODULE_CONTEXT.find(module => module.name === 'community-guardian')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// TODO: rename to community-guardian
const data = MODULE_CONTEXT.find(module => module.name === 'violationDetection')
// src/app/dashboard/community-guardian/page.tsx
// (remove the old TODO and lookup)
- // TODO: rename to community-guardian
- const data = MODULE_CONTEXT.find(module => module.name === 'violationDetection')
+ const data = MODULE_CONTEXT.find(module => module.name === 'community-guardian')


if (!data || data === undefined) {
router.push('/dashboard')
}
Comment on lines +15 to +17
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix the redirect pattern to use useEffect

The current redirect implementation has two issues:

  1. The condition check is redundant (!data || data === undefined)
  2. The redirect is triggered during render, which is not recommended in React

Use the useEffect hook for the redirect:

-  if (!data || data === undefined) {
-    router.push('/dashboard')
-  }
+  useEffect(() => {
+    if (!data) {
+      router.push('/dashboard')
+    }
+  }, [data, router]);

Don't forget to import useEffect:

-import { useRouter } from "next/navigation"
+import { useEffect } from "react"
+import { useRouter } from "next/navigation"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!data || data === undefined) {
router.push('/dashboard')
}
// at the top of src/app/dashboard/community-guardian/page.tsx
import { useEffect } from "react"
import { useRouter } from "next/navigation"
// … inside your component
export default function CommunityGuardianPage() {
const router = useRouter()
const data = /* your existing data fetching logic */
useEffect(() => {
if (!data) {
router.push("/dashboard")
}
}, [data, router])
// … rest of your JSX/render logic
}


return (
<div className="flex flex-col gap-4">
<div className="flex items-center gap-2">
{data && <data.icon size={24} className="text-secondary" />}
<h1 className="text-2xl font-semibold">{data?.title}</h1>
</div>
<p className="text-md text-gray-400">
Neutral, AI-powered Code of Conduct enforcement for safer, healthier communities.
</p>

<div className="flex flex-col gap-4 w-full md:w-1/2">

<p className="text-sm leading-relaxed">Community Guardian helps maintain respectful and inclusive conversations by detecting potential Code of Conduct violations and flagging them early.</p>

<p className="text-sm leading-relaxed">This module is currently in beta and access is limited.</p>

<p className="text-sm leading-relaxed">We're working with selected communities to fine-tune detection, alerts, and recommendations. If you're interested in early access or want to learn more, reach out to us on:</p>

<ul className="flex flex-col gap-2 text-sm">
<li className="w-fit">
<a href="https://discord.gg/QNJGeZeT" target="_blank" className="text-blue-500 hover:text-blue-600 hover:underline flex items-center gap-2" rel="noreferrer">
<FaDiscord />
<span>Discord</span>
</a>
</li>
Comment on lines +39 to +43
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify the Discord link is current

The Discord invite link appears to be a specific channel invitation. These links can expire - verify that this is a permanent invite link that won't break in the future.


🌐 Web query:

Do Discord invite links with format "discord.gg/QNJGeZeT" expire?

💡 Result:

Discord invite links with the format "discord.gg/QNJGeZeT" do not have a single, fixed expiration rule—their validity depends on how the link was configured when it was generated.

By default, most Discord invite links are set to expire after 7 days and have a limited number of uses, but both of these settings can be customized:

  • Expiration Time: When creating an invite, users can select how long the link will be valid (e.g., 30 minutes, 1 hour, 6 hours, 1 day, 7 days, or "never" for a permanent link)[1][2][5].
  • Max Uses: You can also set how many times the link may be used, or choose "No Limit" for unlimited uses[1][2][5].

If a link is created with "Expire After = Never" and "Max Uses = No Limit," the invite will not expire automatically and will remain valid indefinitely, unless it is manually revoked by a server admin[1][2][5].

However, links can expire if:

  • The expiration time is reached.
  • The maximum number of uses is met.
  • The link is manually deleted or revoked by a server admin[1][9].

It’s also worth noting that even links set to "never expire" have sometimes been reported to stop working due to Discord bugs or accidental revocation[3][7][9].

Summary Table

Invite Link Setting Will It Expire?
Default (no changes) Yes (after 7 days or set uses)[1][5]
Expire After set to "Never" & Max Uses "No Limit" No, unless revoked manually[1][2][5]
Manually revoked Yes, immediately[1][9]

Conclusion:
A link like "discord.gg/QNJGeZeT" could expire or not, depending on how it was set up. You must check with the server admin or the context in which you received the link to know for sure. If you receive an invalid or expired message, you will need to request a new link from the server admin[1][4][8].

Citations:


Ensure the Discord invite link won’t expire
By default, Discord invites expire after 7 days and/or a limited number of uses unless explicitly set to “Never” and “No Limit.” Please confirm with your server admin that this invite (discord.gg/QNJGeZeT) has:

  • Expire After = Never
  • Max Uses = No Limit

If it isn’t permanent, ask for or generate a non-expiring invite and update the link here:

  • src/app/dashboard/community-guardian/page.tsx (lines 39–43)

<li className="w-fit">
<a href="https://x.com/together_crew" target="_blank" className="text-blue-500 hover:text-blue-600 hover:underline flex items-center gap-2" rel="noreferrer">
<FaXTwitter />
<span>Twitter</span>
</a>
</li>
<li className="w-fit">
<a href="https://t.me/k_bc0" target="_blank" className="text-blue-500 hover:text-blue-600 hover:underline flex items-center gap-2" rel="noreferrer">
<FaTelegram />
<span>Telegram</span>
</a>
</li>
</ul>

</div>
</div >

)
}
26 changes: 26 additions & 0 deletions src/app/dashboard/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"use client";

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

import ProtectedRoute from "@/components/auth/ProtectedRoute";
import DashboardLayout from "@/components/layouts/DashboardLayout";

import { TokenProvider } from "@/context/TokenContext";

const queryClient = new QueryClient();

export default function RootLayout({ children }: { children: React.ReactNode }) {


return (
<ProtectedRoute>
<QueryClientProvider client={queryClient}>
<TokenProvider>
<DashboardLayout>
{children}
</DashboardLayout>
</TokenProvider>
</QueryClientProvider>
</ProtectedRoute>
)
}
32 changes: 32 additions & 0 deletions src/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use client';

import { FaHome } from "react-icons/fa";

import SEO from "@/components/global/SEO";
import ModuleList from "@/components/home/modules/ModuleList";
import PlatformList from "@/components/home/platforms/PlatformList";

export default function Page() {

return (
<>
<SEO title="Home" />
<div className="flex flex-col gap-6 sm:gap-8">
<h1 className="text-2xl font-semibold flex items-center gap-2">
<FaHome className="text-secondary" />
<span>Home</span>
</h1>
{/* <OnlyAdminWarning /> */}
<div className="flex flex-col md:flex-row gap-6 md:gap-8">
<div className="flex-1">
<PlatformList />
</div>
<div className="flex-1">
<ModuleList />
</div>
</div >
</div>

</ >
);
}
Comment on lines +9 to +32
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Clean dashboard page layout with responsive design

The dashboard page implementation provides a clean, responsive layout that properly integrates both platform and module lists. The use of Tailwind CSS classes for responsive behavior is well-executed.

What is the purpose of the commented-out <OnlyAdminWarning /> component? Is this an incomplete feature or intentionally disabled?


🏁 Script executed:

#!/bin/bash
# Search for OnlyAdminWarning component to understand its context and usage
rg "OnlyAdminWarning" -A 5 -B 5

Length of output: 763


Remove or Implement the OnlyAdminWarning Placeholder

The commented-out <OnlyAdminWarning /> isn’t defined or imported anywhere in the codebase. To keep the dashboard clean:

  • Either remove the commented line from src/app/dashboard/page.tsx
  • Or add the proper import and implementation if this admin-only warning is still required

32 changes: 32 additions & 0 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Inter, Roboto_Mono } from 'next/font/google'

import "@/styles/globals.css";

import { AuthProvider } from '@/context/AuthProvider';

const inter = Inter({
subsets: ['latin'],
display: 'swap',
variable: '--font-inter',
})

const roboto_mono = Roboto_Mono({
subsets: ['latin'],
display: 'swap',
variable: '--font-roboto-mono',
})


export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" data-theme="light">
<body className={`${inter.variable} ${roboto_mono.variable} antialiased`}>
<AuthProvider>
<div className="flex min-h-screen bg-gray-100 overflow-y-hidden">
{children}
</div>
</AuthProvider>
</body>
</html>
)
}
14 changes: 14 additions & 0 deletions src/atoms/authState.atom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { atomWithStorage } from "jotai/utils";

type AuthStateType = {
accessToken: string | null;
refreshToken: string | null;
}

const defaultValue: AuthStateType = {
accessToken: null,
refreshToken: null,
}

// TODO: rename to authState
export const authState = atomWithStorage<AuthStateType>("TC_user", defaultValue);
8 changes: 8 additions & 0 deletions src/atoms/ui.atom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { atomWithStorage } from "jotai/utils";



export const uiAtom = atomWithStorage('ui', {
isSidebarOpen: false,
theme: 'light',
});
31 changes: 18 additions & 13 deletions src/axiosInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ axiosInstance.interceptors.response.use(
error.response?.status === 401 &&
error.config.url?.endsWith('/auth/refresh-tokens')
) {
StorageService.removeLocalStorage('user');
StorageService.removeLocalStorage('community');
StorageService.removeLocalStorage('user');
toast.error('Session expired. Please log in again.', {
position: 'bottom-left',
autoClose: 5000,
Expand All @@ -76,7 +76,7 @@ axiosInstance.interceptors.response.use(
!error.config.url?.endsWith('/auth/refresh-tokens') &&
user
) {
const { accessToken, refreshToken } = user;
const { refreshToken } = user;

if (refreshToken && !isRefreshing) {
isRefreshing = true;
Expand Down Expand Up @@ -124,18 +124,23 @@ axiosInstance.interceptors.response.use(
}
} else {
// Handle no user case
StorageService.removeLocalStorage('user');
StorageService.removeLocalStorage('community');
StorageService.removeLocalStorage('TC_SELECTED_PLATFORM');
StorageService.removeLocalStorage('analysis_state');
toast.error('Token expired...', {
position: 'bottom-left',
autoClose: 5000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: 0,
});
window.location.href = '/';
StorageService.removeLocalStorage('user');
if (window.location.pathname !== '/centric/') {
console.log(window.location.pathname);
toast.error('Token expired...', {
position: 'bottom-left',
autoClose: 5000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: 0,
});
window.location.href = '/centric';
}
}
break;
case 404:
Expand Down
14 changes: 14 additions & 0 deletions src/components/LoadingScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Image from 'next/image';

import tcLogo from '../assets/svg/tc-logo.svg';

export default function LoadingScreen() {
return (
<div className="flex flex-col gap-4 h-screen w-screen items-center justify-center">
<Image src={tcLogo} alt="Logo" width={100} height={100} />
<div>
<span className="loading loading-infinity loading-lg"></span>
</div>
</div>
)
}
23 changes: 23 additions & 0 deletions src/components/auth/ProtectedRoute.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useEffect } from "react";
import { useRouter } from "next/navigation";

import { useAuth } from "@/context/AuthProvider";

import LoadingScreen from "../LoadingScreen";

export default function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { isAuthenticated } = useAuth();
const router = useRouter();

useEffect(() => {
if (!isAuthenticated) {
router.push('/centric'); // TODO: change to /login
}
}, [isAuthenticated, router]);

if (!isAuthenticated) {
return <LoadingScreen />;
}

return children;
}
11 changes: 7 additions & 4 deletions src/components/centric/selectCommunity/TcCommunityItem.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { useState } from "react";
import { Avatar, Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle } from "@mui/material";
import clsx from "clsx";
import { IDiscordModifiedCommunity } from "../../../utils/interfaces";
import { MdGroups } from "react-icons/md";

import TcAvatar from "@/components/shared/TcAvatar";
import { Avatar, Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle } from "@mui/material";

import { conf } from "@/configs";
import { MdGroups } from "react-icons/md";
import { useState } from "react";

import { IDiscordModifiedCommunity } from "../../../utils/interfaces";

interface ITcCommunityItemProps {
community: IDiscordModifiedCommunity;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import React, { useEffect } from 'react';

import { useToken } from '@/context/TokenContext';

import TcCommunityItem from './TcCommunityItem';
import TcText from '../../shared/TcText';
import { StorageService } from '../../../services/StorageService';
import { IDiscordModifiedCommunity } from '../../../utils/interfaces';
import TcCommunityItem from './TcCommunityItem';
import { useToken } from '@/context/TokenContext';

/**
* Props for the TcCommunityListItems component.
Expand Down
Loading
Loading