feat: fix eslint

This commit is contained in:
BlankParticle
2025-05-23 17:53:56 +05:30
parent 6c48934142
commit 3cc07c06f7
35 changed files with 485 additions and 1503 deletions

View File

@@ -22,6 +22,7 @@ RESEND_API_KEY=
# OpenAI API Key
OPENAI_API_KEY=
PERPLEXITY_API_KEY=
#AI PROMPT
AI_SYSTEM_PROMPT=""

View File

@@ -166,22 +166,23 @@ export default function OpenPage() {
}, [initialContributors, additionalContributors]);
const { data: repoData, error: repoError } = useQuery({
queryFn: () => fetch(`https://api.github.com/repos/${REPOSITORY}`).then((res) => res.json()),
queryFn: () =>
fetch(`https://api.github.com/repos/${REPOSITORY}`).then((res) => res.json() as any),
queryKey: ['repo-data', REPOSITORY],
});
const { data: commitsData, error: commitsError } = useQuery({
queryFn: () =>
fetch(`https://api.github.com/repos/${REPOSITORY}/commits?per_page=100`).then((res) =>
res.json(),
fetch(`https://api.github.com/repos/${REPOSITORY}/commits?per_page=100`).then(
(res) => res.json() as any,
),
queryKey: ['commits-data', REPOSITORY],
});
const { data: prsData, error: prsError } = useQuery({
queryFn: () =>
fetch(`https://api.github.com/repos/${REPOSITORY}/pulls?state=open`).then((res) =>
res.json(),
fetch(`https://api.github.com/repos/${REPOSITORY}/pulls?state=open`).then(
(res) => res.json() as any,
),
queryKey: ['prs-data', REPOSITORY],
});
@@ -746,19 +747,6 @@ export default function OpenPage() {
</div>
</div>
<style jsx global>{`
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
`}</style>
<div>
<Tabs defaultValue="grid" className="w-full">
<div className="mb-6 flex justify-center">

View File

@@ -1,267 +0,0 @@
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog';
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from '@/components/ui/accordion';
import { COOKIE_CATEGORIES, type CookieCategory } from '@/lib/cookies';
import { useCookies } from '@/providers/cookie-provider';
import { CookieTrigger } from './cookie-trigger';
import { Button } from '@/components/ui/button';
import { Switch } from '@/components/ui/switch';
import { Label } from '@/components/ui/label';
import { Card } from '@/components/ui/card';
import { useState, useEffect } from 'react';
import { X, Cookie } from 'lucide-react';
interface CookieConsentProps {
children?: React.ReactNode;
showFloatingButton?: boolean;
}
export function CookieConsent({ children, showFloatingButton = true }: CookieConsentProps) {
const [open, setOpen] = useState(false);
const [showBanner, setShowBanner] = useState(false);
const { preferences, updatePreference, acceptAll, rejectAll, isLoaded } = useCookies();
useEffect(() => {
if (isLoaded && !Object.values(preferences).some((value) => value)) {
const timer = setTimeout(() => {
setShowBanner(true);
}, 1000);
return () => clearTimeout(timer);
}
}, [isLoaded, preferences]);
const handleSavePreferences = () => {
setOpen(false);
setShowBanner(false);
};
const handleAcceptAll = () => {
acceptAll();
setOpen(false);
setShowBanner(false);
};
const handleRejectAll = () => {
rejectAll();
setOpen(false);
setShowBanner(false);
};
return (
<>
{showFloatingButton && (
<div className="fixed bottom-4 right-4 z-50">
<CookieTrigger variant="icon" onClick={() => setOpen(true)} />
</div>
)}
<Dialog open={open} onOpenChange={setOpen}>
{children && (
<DialogTrigger asChild onClick={() => setOpen(true)}>
{children}
</DialogTrigger>
)}
<DialogContent className="flex max-h-[90vh] flex-col gap-0 border-zinc-200 bg-white p-0 outline-none dark:border-zinc-800 dark:bg-black">
<div className="border-zinc-200 px-6 py-6 dark:border-zinc-800">
<DialogHeader>
<DialogTitle className="my-2 text-xl text-zinc-900 dark:text-zinc-100">
Cookie Settings
</DialogTitle>
<DialogDescription className="space-y-4">
<span className="block text-sm text-zinc-600 dark:text-zinc-400">
We use cookies and similar technologies to help personalize content, tailor and
measure ads, and provide a better experience. By clicking "Accept All", you
consent to all cookies. You can customize your choices by clicking "Customize" or
reject all optional cookies by clicking "Reject All".
</span>
<span className="block text-sm text-zinc-600 dark:text-zinc-400">
For California residents (CCPA): We do not sell your personal information.
However, some cookies collect data for targeted advertising. To opt out of the
sale of your data for targeted advertising purposes, click "Reject All" or disable
Marketing cookies below.
</span>
<span className="block text-sm text-zinc-600 dark:text-zinc-400">
You can change your preferences at any time by clicking the cookie settings button
in the corner of the screen. For more information about how we use cookies, please
see our{' '}
<a href="/privacy" className="text-blue-500 hover:underline">
Privacy Policy
</a>
.
</span>
</DialogDescription>
</DialogHeader>
</div>
<div className="flex-1 overflow-y-auto px-6 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-zinc-200 dark:[&::-webkit-scrollbar-thumb]:bg-zinc-800 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar]:w-2">
<div className="space-y-6 pb-4">
<Accordion type="multiple" className="space-y-4">
{(
Object.entries(COOKIE_CATEGORIES) as [
CookieCategory,
(typeof COOKIE_CATEGORIES)[CookieCategory],
][]
).map(([category, info]) => (
<AccordionItem
key={category}
value={category}
className="rounded-lg border border-zinc-200 px-4 dark:border-zinc-800"
>
<div className="flex items-center justify-between py-4">
<div className="flex items-center space-x-2">
<Label
htmlFor={category}
className="font-medium text-zinc-900 dark:text-zinc-100"
>
{info.name}
{info.required && (
<span className="ml-2 text-xs text-zinc-600 dark:text-zinc-400">
(Required)
</span>
)}
</Label>
</div>
<Switch
id={category}
checked={preferences[category]}
disabled={info.required}
onCheckedChange={(checked) => updatePreference(category, checked)}
className="data-[state=checked]:bg-blue-600"
/>
</div>
<AccordionTrigger className="mb-2 py-0 text-sm text-zinc-600 hover:no-underline dark:text-zinc-400 [&[data-state=open]>svg]:rotate-180">
More information
</AccordionTrigger>
<AccordionContent className="pb-4 pt-2">
<div className="space-y-3">
<p className="text-sm text-zinc-600 dark:text-zinc-400">
{info.description}
</p>
<div>
<p className="text-sm font-medium text-zinc-700 dark:text-zinc-300">
Duration:
</p>
<p className="text-sm text-zinc-600 dark:text-zinc-400">
{category === 'necessary'
? 'Session - These cookies are deleted when you close your browser'
: category === 'functional'
? '1 year - To remember your preferences'
: category === 'analytics'
? '2 years - To maintain consistent analytics data'
: '90 days - Regular refresh of marketing preferences'}
</p>
</div>
<div>
<p className="text-sm font-medium text-zinc-700 dark:text-zinc-300">
Provider:
</p>
<p className="text-sm text-zinc-600 dark:text-zinc-400">
{category === 'necessary'
? 'First party - Set by us'
: category === 'functional'
? 'First party and selected third parties'
: category === 'analytics'
? 'Google Analytics and similar services'
: 'Various advertising partners'}
</p>
</div>
</div>
</AccordionContent>
</AccordionItem>
))}
</Accordion>
</div>
</div>
<div className="border-t border-zinc-200 bg-white px-6 py-4 dark:border-zinc-800 dark:bg-black">
<div className="flex flex-col-reverse sm:flex-row sm:justify-between sm:space-x-2">
<div className="mt-2 flex flex-1 gap-2 sm:mt-0">
<Button
variant="outline"
className="flex-1 border-zinc-200 bg-transparent text-zinc-900 hover:bg-zinc-100 dark:border-zinc-800 dark:text-zinc-100 dark:hover:bg-zinc-900"
onClick={handleRejectAll}
>
Reject All
</Button>
<Button
variant="outline"
className="flex-1 border-zinc-200 bg-transparent text-zinc-900 hover:bg-zinc-100 dark:border-zinc-800 dark:text-zinc-100 dark:hover:bg-zinc-900"
onClick={handleAcceptAll}
>
Accept All
</Button>
</div>
<Button
onClick={handleSavePreferences}
className="flex-1 bg-black text-white hover:bg-zinc-800 sm:flex-none dark:bg-white dark:text-black dark:hover:bg-white"
>
Save Preferences
</Button>
</div>
</div>
</DialogContent>
</Dialog>
{showBanner && (
<Card className="animate-in fade-in slide-in-from-bottom-4 fixed bottom-4 left-4 right-4 z-40 border-zinc-200 bg-white p-4 shadow-lg duration-300 md:left-auto md:right-4 md:max-w-md dark:border-zinc-800 dark:bg-black">
<div className="mb-4 flex items-start justify-between">
<div className="flex items-center gap-2">
<Cookie className="h-5 w-5 text-zinc-900 dark:text-zinc-100" />
<h3 className="font-semibold text-zinc-900 dark:text-zinc-100">Cookie Preferences</h3>
</div>
<Button
variant="ghost"
size="icon"
onClick={() => setShowBanner(false)}
className="text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-100"
>
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</Button>
</div>
<p className="mb-4 text-sm text-zinc-600 dark:text-zinc-400">
We use cookies to enhance your experience. By continuing to visit this site you agree to
our use of cookies. For California residents: We do not sell personal information, but
some cookies enable targeted advertising.
</p>
<div className="flex flex-wrap gap-2">
<Button
variant="outline"
size="sm"
onClick={() => setOpen(true)}
className="border-zinc-200 bg-transparent text-zinc-900 hover:bg-zinc-100 dark:border-zinc-800 dark:text-zinc-100 dark:hover:bg-zinc-900"
>
Customize
</Button>
<Button
size="sm"
onClick={handleAcceptAll}
className="bg-blue-600 text-white hover:bg-blue-700"
>
Accept All
</Button>
<Button
variant="ghost"
size="sm"
onClick={handleRejectAll}
className="text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-100"
>
Reject All
</Button>
</div>
</Card>
)}
</>
);
}

View File

@@ -1,5 +1,3 @@
'use client';
import React, {
createContext,
forwardRef,
@@ -229,7 +227,7 @@ const Folder = forwardRef<HTMLDivElement, FolderProps & React.HTMLAttributes<HTM
<Accordion.Item {...props} value={value} className="relative h-full overflow-hidden">
<div
className={cn(
`hover:bg-black/10 flex items-center gap-1 rounded-lg px-2 py-1.5 text-sm dark:hover:bg-[#202020]`,
`flex items-center gap-1 rounded-lg px-2 py-1.5 text-sm hover:bg-black/10 dark:hover:bg-[#202020]`,
className,
{
'bg-sidebar-accent rounded-md': isSelect && isSelectable,

View File

@@ -170,7 +170,6 @@ const createVariantsWithTransition = (
visible: {
...baseVariants.visible,
transition: {
// @ts-expect-error, fix later if needed
...(hasTransition(baseVariants.visible) ? baseVariants.visible.transition : {}),
...mainTransition,
},
@@ -178,7 +177,6 @@ const createVariantsWithTransition = (
exit: {
...baseVariants.exit,
transition: {
// @ts-expect-error, fix later if needed
...(hasTransition(baseVariants.exit) ? baseVariants.exit.transition : {}),
...mainTransition,
staggerDirection: -1,

View File

@@ -1,5 +1,3 @@
'use client';
import { Switch as SwitchPrimitives } from 'radix-ui';
import * as React from 'react';

View File

@@ -1,36 +0,0 @@
// import { FlatCompat } from "@eslint/eslintrc";
// import baseConfig from "@zero/eslint-config";
// import { dirname, resolve } from "path";
// import { fileURLToPath } from "url";
// const __filename = fileURLToPath(import.meta.url);
// const __dirname = dirname(__filename);
// const compat = new FlatCompat({
// baseDirectory: __dirname,
// resolvePluginsRelativeTo: __dirname,
// });
// /** @type {import("eslint").Linter.Config[]} */
// const eslintConfig = [
// ...baseConfig,
// {
// files: ["**/*.{ts,tsx}"],
// languageOptions: {
// parserOptions: {
// project: resolve(__dirname, "./tsconfig.json"),
// tsconfigRootDir: __dirname,
// },
// },
// settings: {
// "import/resolver": {
// typescript: {
// project: resolve(__dirname, "./tsconfig.json"),
// },
// },
// },
// },
// ...compat.extends("next/core-web-vitals"),
// ];
// export default eslintConfig;

View File

@@ -0,0 +1,3 @@
import config from '@zero/eslint-config';
export default config;

View File

@@ -1,137 +0,0 @@
import type { CookieCategory } from './cookies';
interface CookieOptions {
category: CookieCategory;
path?: string;
domain?: string;
secure?: boolean;
sameSite?: 'Strict' | 'Lax' | 'None';
expires?: Date;
}
const DEFAULT_OPTIONS = {
path: '/',
maxAge: 365 * 24 * 60 * 60,
};
class CookieUtils {
private static cookieRegistry: Map<string, CookieCategory> = new Map();
static registerCookie(name: string, category: CookieCategory) {
this.cookieRegistry.set(name, category);
}
static getCookieCategory(name: string): CookieCategory | undefined {
return this.cookieRegistry.get(name);
}
static setCookie(name: string, value: string, options: CookieOptions): void {
const cookieString = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
const cookieOptions: string[] = [];
if (options.path) cookieOptions.push(`path=${options.path}`);
if (options.domain) cookieOptions.push(`domain=${options.domain}`);
if (options.secure) cookieOptions.push('secure');
if (options.sameSite) cookieOptions.push(`samesite=${options.sameSite}`);
if (options.expires) cookieOptions.push(`expires=${options.expires.toUTCString()}`);
document.cookie = `${cookieString}${cookieOptions.length ? '; ' + cookieOptions.join('; ') : ''}`;
}
static getCookie(name: string): string | null {
const cookies = document.cookie.split(';');
for (const cookie of cookies) {
const parts = cookie.split('=').map((c) => c.trim());
const cookieName = parts[0];
const cookieValue = parts[1];
if (cookieName === name && cookieValue !== undefined) {
return decodeURIComponent(cookieValue);
}
}
return null;
}
static deleteCookie(name: string, options?: Pick<CookieOptions, 'path' | 'domain'>): void {
const opts: CookieOptions = {
...options,
category: 'necessary',
expires: new Date(0),
};
this.setCookie(name, '', opts);
}
static getAllCookies(): { [key: string]: string } {
return document.cookie.split(';').reduce(
(acc, cookie) => {
const [name, value] = cookie.split('=').map((c) => c.trim());
if (name && value) {
acc[name] = decodeURIComponent(value);
}
return acc;
},
{} as { [key: string]: string },
);
}
static removeAllCookiesByCategory(category: CookieCategory) {
const allCookies = this.getAllCookies();
Object.keys(allCookies).forEach((cookieName) => {
const cookieCategory = this.getCookieCategory(cookieName);
if (cookieCategory === category) {
this.deleteCookie(cookieName, { path: '/' });
}
});
}
static cleanupRejectedCookies(acceptedCategories: CookieCategory[]): void {
const cookieMapping: Record<string, CookieCategory> = {
_ga: 'analytics',
_gid: 'analytics',
_fbp: 'marketing',
// TODO: Add more cookie mappings as needed
};
const cookies = document.cookie.split(';');
for (const cookie of cookies) {
const parts = cookie.split('=').map((c) => c.trim());
const cookieName = parts[0];
if (
cookieName &&
cookieMapping[cookieName] &&
!acceptedCategories.includes(cookieMapping[cookieName])
) {
this.deleteCookie(cookieName, { path: '/' });
}
}
}
static cleanupMarketingCookies(): void {
const marketingCookies = [
'_fbp',
'_gcl_au',
'_uetsid',
'_uetvid',
// TODO: Add more marketing cookie names as needed
];
marketingCookies.forEach((cookieName) => {
this.deleteCookie(cookieName, { path: '/' });
});
}
static getCookiesByCategory(category: CookieCategory): string[] {
const cookieMapping: Record<string, CookieCategory> = {
_ga: 'analytics',
_gid: 'analytics',
_fbp: 'marketing',
// TODO:Add more cookie mappings as needed
};
return Object.entries(cookieMapping)
.filter(([_, cookieCategory]) => cookieCategory === category)
.map(([cookieName]) => cookieName);
}
}
export default CookieUtils;

View File

@@ -1,48 +0,0 @@
import { TrackingCategory } from '@coinbase/cookie-manager';
export type CookieCategory = 'necessary' | 'functional' | 'analytics' | 'marketing';
export interface CategoryInfo {
name: string;
description: string;
required?: boolean;
trackingCategory: TrackingCategory;
}
export interface CookiePreferences {
necessary: boolean;
analytics: boolean;
marketing: boolean;
preferences: boolean;
}
export const COOKIE_CATEGORIES: Record<CookieCategory, CategoryInfo> = {
necessary: {
name: 'Strictly Necessary',
description:
'These cookies are essential for the website to function properly and cannot be switched off. They are usually only set in response to actions made by you such as setting your privacy preferences, logging in, or filling in forms.',
required: true,
trackingCategory: TrackingCategory.NECESSARY,
},
functional: {
name: 'Functional',
description:
'These cookies enable the website to provide enhanced functionality and personalization. They may be set by us or by third-party providers whose services we have added to our pages. If you do not allow these cookies, some or all of these services may not function properly.',
trackingCategory: TrackingCategory.FUNCTIONAL,
},
analytics: {
name: 'Analytics & Performance',
description:
'These cookies allow us to count visits and traffic sources so we can measure and improve the performance of our site. They help us to know which pages are the most and least popular and see how visitors move around the site. All information these cookies collect is aggregated and therefore anonymous. If you do not allow these cookies we will not know when you have visited our site.',
trackingCategory: TrackingCategory.PERFORMANCE,
},
marketing: {
name: 'Marketing & Targeting',
description:
'These cookies may be set through our site by our advertising partners. They may be used by those companies to build a profile of your interests and show you relevant adverts on other sites. They do not store directly personal information but are based on uniquely identifying your browser and internet device. If you do not allow these cookies, you will experience less targeted advertising.',
trackingCategory: TrackingCategory.TARGETING,
},
};
export const COOKIE_CONSENT_KEY = 'cookieConsent';
export const COOKIE_PREFERENCES_KEY = 'cookiePreferences';

View File

@@ -1,18 +0,0 @@
// add flags.ts
import { statsigAdapter, type StatsigUser } from "@flags-sdk/statsig";
import { flag, dedupe } from "flags/next";
import type { Identify } from "flags";
export const identify = dedupe((async () => ({
customIDs: { userID: Math.random().toString(36).slice(2) },
// add any additional user properties you collect here
})) satisfies Identify<StatsigUser>);
export const createFeatureGate = (key: string) => flag<boolean, StatsigUser>({
key,
adapter: statsigAdapter.featureGate((gate) => gate.value, { exposureLogging: true }),
identify,
decide() {
return Math.random() > 0.1;
},
});

View File

@@ -8,7 +8,8 @@
"build": "react-router build",
"start": "wrangler dev --port 3000 --show-interactive-dev-session=false",
"types": "wrangler types",
"deploy": "wrangler deploy"
"deploy": "wrangler deploy",
"lint": "eslint ."
},
"dependencies": {
"@11labs/react": "^0.1.3",
@@ -43,7 +44,6 @@
"@trpc/server": "catalog:",
"@trpc/tanstack-react-query": "catalog:",
"@zero/db": "workspace:*",
"@zero/eslint-config": "workspace:*",
"@zero/server": "workspace:*",
"accept-language-parser": "^1.5.0",
"ai": "^4.3.9",
@@ -110,7 +110,6 @@
},
"devDependencies": {
"@cloudflare/vite-plugin": "^1.2.0",
"@eslint/eslintrc": "3.3.0",
"@tailwindcss/typography": "0.5.16",
"@types/accept-language-parser": "^1.5.8",
"@types/canvas-confetti": "1.9.0",
@@ -119,13 +118,11 @@
"@types/react": "19.0.10",
"@types/react-dom": "19.0.4",
"@types/sanitize-html": "2.13.0",
"@typescript-eslint/eslint-plugin": "8.26.1",
"@typescript-eslint/parser": "8.26.1",
"@zero/eslint-config": "workspace:*",
"@zero/tsconfig": "workspace:*",
"drizzle-kit": "0.31.1",
"eslint": "9.22.0",
"eslint-plugin-react": "7.33.2",
"eslint-plugin-react-hooks": "5.2.0",
"eslint": "^9.27.0",
"jiti": "2.4.2",
"postcss": "8.5.3",
"remeda": "2.21.3",
"tailwind-scrollbar": "3.1.0",

View File

@@ -1,142 +0,0 @@
import { createContext, useContext, useEffect, useMemo, useState } from 'react';
import { COOKIE_CATEGORIES, type CookieCategory } from '@/lib/cookies';
import CookieUtils from '@/lib/cookie-utils';
interface CookiePreferences {
[key: string]: boolean;
}
interface CookieContextType {
preferences: CookiePreferences;
isLoaded: boolean;
updatePreference: (category: CookieCategory, value: boolean) => void;
acceptAll: () => void;
rejectAll: () => void;
hasConsent: boolean;
}
const CookieContext = createContext<CookieContextType | null>(null);
const PREFERENCES_COOKIE = 'cookie_preferences';
export function CookieProvider({ children }: { children: React.ReactNode }) {
const [preferences, setPreferences] = useState<CookiePreferences>(() =>
Object.keys(COOKIE_CATEGORIES).reduce(
(acc, key) => ({
...acc,
[key]: COOKIE_CATEGORIES[key as CookieCategory].required || false,
}),
{},
),
);
const [isLoaded, setIsLoaded] = useState(false);
useEffect(() => {
const loadPreferences = () => {
const storedPrefs = CookieUtils.getCookie(PREFERENCES_COOKIE);
if (storedPrefs) {
try {
const parsedPrefs = JSON.parse(storedPrefs);
Object.entries(COOKIE_CATEGORIES).forEach(([key, info]) => {
if (info.required) {
parsedPrefs[key] = true;
}
});
setPreferences(parsedPrefs);
} catch (error) {
console.error('Error parsing cookie preferences:', error);
}
}
setIsLoaded(true);
};
loadPreferences();
}, []);
const savePreferences = (newPreferences: CookiePreferences) => {
// Store preferences with a 1-year expiry for GDPR compliance
const oneYear = 365 * 24 * 60 * 60 * 1000;
CookieUtils.setCookie(PREFERENCES_COOKIE, JSON.stringify(newPreferences), {
category: 'necessary',
expires: new Date(Date.now() + oneYear),
sameSite: 'Lax',
secure: true,
});
const acceptedCategories = Object.entries(newPreferences)
.filter(([_, accepted]) => accepted)
.map(([category]) => category as CookieCategory);
CookieUtils.cleanupRejectedCookies(acceptedCategories);
logConsent(newPreferences);
};
const logConsent = (preferences: CookiePreferences) => {
const consentData = {
timestamp: new Date().toISOString(),
preferences,
userAgent: navigator.userAgent,
// TODO: Add any other relevant data for compliance
};
console.log('Cookie consent logged:', consentData);
};
const updatePreference = (category: CookieCategory, value: boolean) => {
const categoryInfo = COOKIE_CATEGORIES[category];
if (categoryInfo.required) return;
const newPreferences = { ...preferences, [category]: value };
setPreferences(newPreferences);
savePreferences(newPreferences);
if (category === 'marketing' && !value) {
CookieUtils.cleanupMarketingCookies();
}
};
const acceptAll = () => {
const newPreferences = Object.keys(COOKIE_CATEGORIES).reduce(
(acc, key) => ({ ...acc, [key]: true }),
{},
);
setPreferences(newPreferences);
savePreferences(newPreferences);
};
const rejectAll = () => {
const newPreferences = Object.keys(COOKIE_CATEGORIES).reduce(
(acc, key) => ({
...acc,
[key]: COOKIE_CATEGORIES[key as CookieCategory].required || false,
}),
{},
);
setPreferences(newPreferences);
savePreferences(newPreferences);
};
const hasConsent = useMemo(
() => Object.values(preferences).some((value) => value),
[preferences],
);
const value = {
preferences,
isLoaded,
updatePreference,
acceptAll,
rejectAll,
hasConsent,
};
return <CookieContext.Provider value={value}>{children}</CookieContext.Provider>;
}
export function useCookies() {
const context = useContext(CookieContext);
if (!context) {
throw new Error('useCookies must be used within a CookieProvider');
}
return context;
}

View File

@@ -1,21 +1,8 @@
{
"extends": "@zero/tsconfig/base",
"compilerOptions": {
"target": "ESNext",
"lib": ["dom", "dom.iterable", "esnext"],
"types": ["node", "vite/client"],
"allowJs": true,
"checkJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"verbatimModuleSyntax": true,
"paths": {
"@/*": ["./*"]
},

View File

@@ -0,0 +1,3 @@
import config from '@zero/eslint-config';
export default config;

View File

@@ -5,7 +5,8 @@
"scripts": {
"dev": "wrangler dev --show-interactive-dev-session=false --experimental-vectorize-bind-to-prod --env local",
"deploy": "wrangler deploy",
"types": "wrangler types --env local"
"types": "wrangler types --env local",
"lint": "eslint ."
},
"exports": {
"./trpc": "./src/trpc/index.ts",
@@ -15,6 +16,7 @@
"dependencies": {
"@ai-sdk/google": "^1.2.18",
"@ai-sdk/openai": "^1.3.21",
"@ai-sdk/ui-utils": "1.2.11",
"@coinbase/cookie-manager": "1.1.8",
"@googleapis/gmail": "12.0.0",
"@googleapis/people": "3.0.9",
@@ -58,6 +60,10 @@
"@types/node": "^22.9.0",
"@types/react": "19.0.10",
"@types/sanitize-html": "2.13.0",
"@zero/eslint-config": "workspace:*",
"@zero/tsconfig": "workspace:*",
"eslint": "^9.27.0",
"jiti": "2.4.2",
"typescript": "catalog:"
}
}

View File

@@ -1,5 +1,3 @@
import { isToday, isThisMonth, differenceInCalendarMonths } from 'date-fns';
import type { JSONContent } from 'novel';
import type { Sender } from '../types';
export const FOLDERS = {
@@ -43,13 +41,6 @@ export const getFolderTags = (folder: string): string[] => {
return FOLDER_TAGS[folder] || [];
};
export const getCookie = (key: string): string | null => {
const cookies = Object.fromEntries(
document.cookie.split('; ').map((v) => v.split(/=(.*)/s).map(decodeURIComponent)),
);
return cookies?.[key] ?? null;
};
export const cleanEmailAddress = (email: string = '') => {
return email.replace(/[<>]/g, '').trim();
};
@@ -101,112 +92,6 @@ export const getFileIcon = (mimeType: string): string => {
return '📎'; // Default icon
};
export const createAIJsonContent = (text: string): JSONContent => {
// Try to identify common sign-off patterns with a more comprehensive regex
const signOffPatterns = [
/\b((?:Best regards|Regards|Sincerely|Thanks|Thank you|Cheers|Best|All the best|Yours truly|Yours sincerely|Kind regards|Cordially)(?:,)?)\s*\n+\s*([A-Za-z][A-Za-z\s.]*)$/i,
];
let mainContent = text;
let signatureLines: string[] = [];
// Extract sign-off if found
for (const pattern of signOffPatterns) {
const match = text.match(pattern);
if (match) {
// Find the index where the sign-off starts
const signOffIndex = text.lastIndexOf(match[0]);
if (signOffIndex > 0) {
// Split the content
mainContent = text.substring(0, signOffIndex).trim();
// Split the signature part into separate lines
const signature = text.substring(signOffIndex).trim();
signatureLines = signature
.split(/\n+/)
.map((line) => line.trim())
.filter(Boolean);
break;
}
}
}
// If no signature was found with regex but there are newlines at the end,
// check if the last lines could be a signature
if (signatureLines.length === 0) {
const allLines = text.split(/\n+/);
if (allLines.length > 1) {
// Check if last 1-3 lines might be a signature (short lines at the end)
const potentialSigLines = allLines
.slice(-3)
.filter(
(line) =>
line.trim().length < 60 && !line.trim().endsWith('?') && !line.trim().endsWith('.'),
);
if (potentialSigLines.length > 0) {
signatureLines = potentialSigLines;
mainContent = allLines
.slice(0, allLines.length - potentialSigLines.length)
.join('\n')
.trim();
}
}
}
// Split the main content into paragraphs
const paragraphs = mainContent
.split(/\n\s*\n/)
.map((p) => p.trim())
.filter(Boolean);
if (paragraphs.length === 0 && signatureLines.length === 0) {
// If no paragraphs and no signature were found, treat the whole text as one paragraph
paragraphs.push(text);
}
// Create a content array with appropriate spacing between paragraphs
const content = [];
paragraphs.forEach((paragraph, index) => {
// Add the content paragraph
content.push({
type: 'paragraph',
content: [{ type: 'text', text: paragraph }],
});
// Add an empty paragraph between main paragraphs
if (index < paragraphs.length - 1) {
content.push({
type: 'paragraph',
});
}
});
// If we found a signature, add it with proper spacing
if (signatureLines.length > 0) {
// Add spacing before the signature if there was content
if (paragraphs.length > 0) {
content.push({
type: 'paragraph',
});
}
// Add each line of the signature as a separate paragraph
signatureLines.forEach((line) => {
content.push({
type: 'paragraph',
content: [{ type: 'text', text: line }],
});
});
}
return {
type: 'doc',
content: content,
};
};
export const generateConversationId = (): string => {
return `conv_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
};

8
apps/server/src/overrides.d.ts vendored Normal file
View File

@@ -0,0 +1,8 @@
declare namespace Cloudflare {
declare interface Env {
zero: Fetcher & {
subscribe: (data: { connectionId: string; providerId: string }) => Promise<void>;
unsubscribe: (data: { connectionId: string; providerId: string }) => Promise<void>;
};
}
}

View File

@@ -1,32 +1,4 @@
{
"compilerOptions": {
// Environment setup & latest features
"lib": ["ESNext", "DOM"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",
"jsx": "preserve",
"allowJs": true,
// Bundler mode
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"emitDeclarationOnly": true,
// Best practices
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
// Some stricter flags (disabled by default)
"noUnusedLocals": true,
"noUnusedParameters": true,
"noPropertyAccessFromIndexSignature": true,
"composite": true,
"types": ["@types/node"]
},
"include": ["src/**/*.ts", "worker-configuration.d.ts"]
"extends": "@zero/tsconfig/base",
"include": ["src/**/*.ts", "src/overrides.d.ts", "worker-configuration.d.ts"]
}

View File

@@ -6,6 +6,7 @@
"packageManager": "pnpm@10.11.0",
"devDependencies": {
"@clack/prompts": "0.10.1",
"@zero/tsconfig": "workspace:*",
"tiny-glob": "0.2.9"
}
}

View File

@@ -1,12 +1,3 @@
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"target": "ESNext",
"verbatimModuleSyntax": true,
"noEmit": true,
"skipLibCheck": true,
"esModuleInterop": true
}
"extends": "@zero/tsconfig/base"
}

View File

@@ -1,10 +1,5 @@
{
"extends": "@zero/tsconfig/react-library.json",
"include": [
"src",
"migrations"
],
"exclude": [
"node_modules"
]
}
"extends": "@zero/tsconfig/base",
"include": ["src", "migrations"],
"exclude": ["node_modules"]
}

View File

@@ -0,0 +1,16 @@
import { defineConfig, globalIgnores } from 'eslint/config';
import reactHooksPlugin from 'eslint-plugin-react-hooks';
import tseslint from 'typescript-eslint';
export default defineConfig([
globalIgnores([
'**/node_modules/**',
'**/dist/**',
'**/build/**',
'**/.react-router/**',
'**/.well-known/**',
]),
// @ts-expect-error
tseslint.configs.recommended,
reactHooksPlugin.configs['recommended-latest'],
]);

View File

@@ -1,76 +0,0 @@
import reactHooksPlugin from "eslint-plugin-react-hooks";
import tsPlugin from "@typescript-eslint/eslint-plugin";
import tsParser from "@typescript-eslint/parser";
import eslint from "eslint";
/** @type {import("eslint").Linter.Config[]} */
const config = [
{
ignores: ["**/node_modules/**", "**/dist/**", "**/.next/**", "**/.well-known/**"],
},
{
files: ["**/*.{js,mjs,cjs}"],
languageOptions: {
ecmaVersion: "latest",
sourceType: "module",
},
},
{
files: ["**/*.{ts,tsx,js,jsx}"],
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
project: true,
},
},
plugins: {
// @ts-expect-error - tsPlugin doesn't exactly match the type ESLint.Plugin
"@typescript-eslint": tsPlugin,
"react-hooks": reactHooksPlugin,
},
rules: {
// Recommended lints
"react-hooks/exhaustive-deps": "warn",
"@typescript-eslint/no-misused-promises": [
2,
{
checksVoidReturn: {
attributes: false,
},
},
],
"@typescript-eslint/no-unused-vars": [
"warn",
{
vars: "all",
args: "after-used",
caughtErrors: "all",
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
ignoreRestSiblings: false,
destructuredArrayIgnorePattern: "^_",
},
],
// Strict lints
"@typescript-eslint/consistent-type-imports": [
"error",
{
prefer: "type-imports",
fixStyle: "inline-type-imports",
disallowTypeAnnotations: true, // This makes non-type imports for types an error
},
],
"react-hooks/rules-of-hooks": "error",
"@typescript-eslint/await-thenable": "error",
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-non-null-assertion": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "error",
},
},
];
export default config;

View File

@@ -1 +0,0 @@
export { default } from "./eslint.config.mjs";

View File

@@ -1,19 +1,20 @@
{
"name": "@zero/eslint-config",
"version": "0.0.0",
"private": true,
"main": "index.js",
"type": "module",
"exports": {
".": "./config.ts"
},
"devDependencies": {
"@types/eslint": "^9.6.1",
"@typescript-eslint/eslint-plugin": "^8.26.1",
"@typescript-eslint/parser": "^8.26.1",
"eslint": "^9.22.0",
"eslint-config-next": "^14.1.0",
"eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-typescript": "^3.6.1",
"@typescript-eslint/eslint-plugin": "^8.32.1",
"@typescript-eslint/parser": "^8.32.1",
"eslint": "^9.27.0",
"eslint-import-resolver-typescript": "^4.3.5",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0"
},
"dependencies": {
"typescript-eslint": "8.32.1"
}
}

View File

@@ -1,10 +1,10 @@
{
"extends": "@zero/tsconfig/base.json",
"extends": "@zero/tsconfig/base",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"noEmit": true
},
"include": ["eslint.config.mjs", "index.js"],
"exclude": ["node_modules", "dist"]
"include": ["**/*.ts"],
"exclude": ["node_modules"]
}

View File

@@ -1,5 +1,5 @@
{
"extends": "@zero/tsconfig/base.json",
"extends": "@zero/tsconfig/base",
"include": ["."],
"exclude": ["dist", "build", "node_modules"]
}

View File

@@ -1,23 +1,23 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"declaration": false,
"esModuleInterop": true,
"incremental": false,
"isolatedModules": true,
"lib": [
"es2022",
"DOM",
"DOM.Iterable"
],
"module": "NodeNext",
"moduleDetection": "force",
"moduleResolution": "NodeNext",
"noUncheckedIndexedAccess": true,
"resolveJsonModule": true,
"target": "ESNext",
"lib": ["EsNext"],
"allowJs": true,
"checkJs": true,
"skipLibCheck": true,
"strict": true,
"target": "ES2022",
"verbatimModuleSyntax": false
"noEmit": true,
"esModuleInterop": true,
"module": "ESNext",
"moduleResolution": "Bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"verbatimModuleSyntax": true,
"allowImportingTsExtensions": true,
"allowArbitraryExtensions": true,
"types": ["node"]
}
}

View File

@@ -1,12 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "./base.json",
"compilerOptions": {
"plugins": [{ "name": "next" }],
"module": "ESNext",
"moduleResolution": "Bundler",
"allowJs": true,
"jsx": "preserve",
"noEmit": true
}
}

View File

@@ -1,9 +1,7 @@
{
"name": "@zero/tsconfig",
"version": "1.0.0",
"private": true,
"license": "MIT",
"publishConfig": {
"access": "public"
"exports": {
"./base": "./base.json"
}
}

View File

@@ -1,7 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "./base.json",
"compilerOptions": {
"jsx": "react-jsx"
}
}

886
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,14 +3,14 @@ packages:
- packages/*
- scripts/*
catalog:
zod: "^3.24.4"
better-auth: "^1.2.8"
autumn-js: "^0.0.36"
superjson: "^2.2.2"
"@trpc/server": "^11.1.2"
"@trpc/client": "^11.1.2"
"@trpc/tanstack-react-query": "^11.1.2"
wrangler: "^4.16.0"
typescript: "^5.8.3"
zod: ^3.24.4
better-auth: ^1.2.8
autumn-js: ^0.0.36
superjson: ^2.2.2
'@trpc/server': ^11.1.2
'@trpc/client': ^11.1.2
'@trpc/tanstack-react-query': ^11.1.2
wrangler: ^4.16.0
typescript: ^5.8.3
patchedDependencies:
novel: patches/novel.patch

View File

@@ -1,3 +1,3 @@
{
"extends": "@zero/tsconfig/base.json"
"extends": "@zero/tsconfig/base"
}