chore: remove 62 tsc emit artifacts from src/, add noEmit to tsconfig
tsc was emitting compiled .js files directly into packages/web/src/ alongside the .tsx source files because noEmit was not set. These artifacts were never used — Vite compiles from .tsx source directly. - Add noEmit: true to packages/web/tsconfig.json (tsc = type-check only) - Delete all 62 orphaned .js files from src/ (-6,129 lines) - Add packages/web/src/**/*.js to .gitignore as safeguard
This commit is contained in:
@@ -1,40 +0,0 @@
|
||||
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
||||
import { useUIStore } from '../../stores/uiStore';
|
||||
const statusColors = {
|
||||
online: 'bg-discord-green',
|
||||
idle: 'bg-discord-yellow',
|
||||
dnd: 'bg-discord-red',
|
||||
offline: 'bg-discord-text-muted',
|
||||
};
|
||||
export function Avatar({ src, name, size = 40, status, className = '', onClick, user }) {
|
||||
const openUserProfile = useUIStore((s) => s.openUserProfile);
|
||||
const initials = name.charAt(0).toUpperCase();
|
||||
const fontSize = size < 32 ? 'text-xs' : size < 48 ? 'text-sm' : 'text-lg';
|
||||
const handleClick = (e) => {
|
||||
if (onClick) {
|
||||
onClick(e);
|
||||
}
|
||||
else if (user) {
|
||||
e.stopPropagation();
|
||||
const rect = e.currentTarget.getBoundingClientRect();
|
||||
openUserProfile(user, {
|
||||
top: Math.min(rect.top, window.innerHeight - 450),
|
||||
left: rect.right + 16,
|
||||
});
|
||||
}
|
||||
};
|
||||
return (_jsxs("div", { className: `relative inline-flex flex-shrink-0 ${(onClick || user) ? 'cursor-pointer' : ''} ${className}`, style: { width: size, height: size }, onClick: handleClick, children: [src ? (_jsx("img", { src: src.startsWith('http') ? src : `/api/uploads/${src}`, alt: name, className: "w-full h-full rounded-full object-cover", onError: (e) => {
|
||||
e.target.style.display = 'none';
|
||||
const parent = e.target.parentElement;
|
||||
if (parent) {
|
||||
const fallback = parent.querySelector('.avatar-fallback');
|
||||
if (fallback)
|
||||
fallback.style.display = 'flex';
|
||||
}
|
||||
} })) : null, _jsx("div", { className: `avatar-fallback w-full h-full rounded-full bg-discord-blurple flex items-center justify-center ${fontSize} font-semibold text-white ${src ? 'hidden' : 'flex'}`, style: src ? { display: 'none' } : undefined, children: initials }), status && (_jsx("div", { className: `absolute -bottom-0.5 -right-0.5 rounded-full border-[3px] border-discord-bg-secondary ${statusColors[status] ?? 'bg-discord-text-muted'}`, style: {
|
||||
width: size * 0.35,
|
||||
height: size * 0.35,
|
||||
minWidth: 12,
|
||||
minHeight: 12,
|
||||
} }))] }));
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
export function ContextMenu({ items, children }) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [position, setPosition] = useState({ x: 0, y: 0 });
|
||||
const menuRef = useRef(null);
|
||||
const handleContextMenu = (e) => {
|
||||
e.preventDefault();
|
||||
setPosition({ x: e.clientX, y: e.clientY });
|
||||
setIsOpen(true);
|
||||
};
|
||||
useEffect(() => {
|
||||
const handleClick = () => setIsOpen(false);
|
||||
const handleScroll = () => setIsOpen(false);
|
||||
if (isOpen) {
|
||||
document.addEventListener('click', handleClick);
|
||||
document.addEventListener('scroll', handleScroll, true);
|
||||
return () => {
|
||||
document.removeEventListener('click', handleClick);
|
||||
document.removeEventListener('scroll', handleScroll, true);
|
||||
};
|
||||
}
|
||||
}, [isOpen]);
|
||||
// Adjust position to keep menu in viewport
|
||||
useEffect(() => {
|
||||
if (isOpen && menuRef.current) {
|
||||
const rect = menuRef.current.getBoundingClientRect();
|
||||
const newPosition = { ...position };
|
||||
if (rect.right > window.innerWidth) {
|
||||
newPosition.x = window.innerWidth - rect.width - 8;
|
||||
}
|
||||
if (rect.bottom > window.innerHeight) {
|
||||
newPosition.y = window.innerHeight - rect.height - 8;
|
||||
}
|
||||
if (newPosition.x !== position.x || newPosition.y !== position.y) {
|
||||
setPosition(newPosition);
|
||||
}
|
||||
}
|
||||
}, [isOpen, position]);
|
||||
return (_jsxs(_Fragment, { children: [_jsx("div", { onContextMenu: handleContextMenu, children: children }), isOpen && (_jsx("div", { ref: menuRef, className: "fixed z-50 min-w-[180px] py-1.5 bg-discord-bg-floating rounded-md shadow-elevation-high animate-fade-in", style: { left: position.x, top: position.y }, children: items.map((item, i) => (_jsxs("button", { className: `w-full text-left px-2 py-1.5 mx-1.5 text-sm rounded-sm flex items-center gap-2 ${item.danger
|
||||
? 'text-discord-red hover:bg-discord-red hover:text-white'
|
||||
: 'text-discord-text-secondary hover:bg-discord-blurple hover:text-white'}`, style: { width: 'calc(100% - 12px)' }, onClick: (e) => {
|
||||
e.stopPropagation();
|
||||
item.onClick();
|
||||
setIsOpen(false);
|
||||
}, children: [item.icon && _jsx("span", { className: "w-4 h-4", children: item.icon }), item.label] }, i))) }))] }));
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
||||
export function LoadingSpinner({ size = 40, className = '' }) {
|
||||
return (_jsx("div", { className: `flex items-center justify-center ${className}`, children: _jsxs("svg", { className: "animate-spin", width: size, height: size, viewBox: "0 0 24 24", fill: "none", children: [_jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), _jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" })] }) }));
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
||||
import { useEffect, useCallback } from 'react';
|
||||
export function Modal({ isOpen, onClose, title, children, maxWidth = 'max-w-md' }) {
|
||||
const handleKeyDown = useCallback((e) => {
|
||||
if (e.key === 'Escape') {
|
||||
onClose();
|
||||
}
|
||||
}, [onClose]);
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
return () => document.removeEventListener('keydown', handleKeyDown);
|
||||
}
|
||||
}, [isOpen, handleKeyDown]);
|
||||
if (!isOpen)
|
||||
return null;
|
||||
return (_jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center animate-fade-in", children: [_jsx("div", { className: "absolute inset-0 bg-discord-bg-overlay", onClick: onClose }), _jsxs("div", { className: `relative ${maxWidth} w-full mx-4 bg-discord-bg-surface rounded-lg shadow-xl animate-slide-up`, children: [title && (_jsxs("div", { className: "flex items-center justify-between px-4 pt-4", children: [_jsx("h2", { className: "text-xl font-bold text-discord-text-primary", children: title }), _jsx("button", { onClick: onClose, className: "text-discord-text-muted hover:text-discord-text-primary transition-colors p-1", children: _jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M18.4 4L12 10.4L5.6 4L4 5.6L10.4 12L4 18.4L5.6 20L12 13.6L18.4 20L20 18.4L13.6 12L20 5.6L18.4 4Z" }) }) })] })), _jsx("div", { className: "p-4", children: children })] })] }));
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
export function Tooltip({ content, children, position = 'right', delay = 200 }) {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const timeoutRef = useRef();
|
||||
const show = () => {
|
||||
timeoutRef.current = setTimeout(() => setIsVisible(true), delay);
|
||||
};
|
||||
const hide = () => {
|
||||
if (timeoutRef.current)
|
||||
clearTimeout(timeoutRef.current);
|
||||
setIsVisible(false);
|
||||
};
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (timeoutRef.current)
|
||||
clearTimeout(timeoutRef.current);
|
||||
};
|
||||
}, []);
|
||||
const positionClasses = {
|
||||
top: 'bottom-full left-1/2 -translate-x-1/2 mb-2',
|
||||
right: 'left-full top-1/2 -translate-y-1/2 ml-2',
|
||||
bottom: 'top-full left-1/2 -translate-x-1/2 mt-2',
|
||||
left: 'right-full top-1/2 -translate-y-1/2 mr-2',
|
||||
};
|
||||
return (_jsxs("div", { className: "relative inline-flex", onMouseEnter: show, onMouseLeave: hide, children: [children, isVisible && (_jsx("div", { className: `absolute z-50 px-3 py-1.5 text-sm font-medium text-discord-text-primary bg-discord-bg-floating rounded-md shadow-elevation-high whitespace-nowrap pointer-events-none ${positionClasses[position]}`, children: content }))] }));
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Avatar } from '../ui/Avatar';
|
||||
import { api } from '../../api/client';
|
||||
import { useServerStore } from '../../stores/serverStore';
|
||||
import { useUIStore } from '../../stores/uiStore';
|
||||
export function UserProfilePopout({ user, onClose, position }) {
|
||||
const navigate = useNavigate();
|
||||
const addDmChannel = useServerStore((s) => s.addDmChannel);
|
||||
const displayName = user.displayName ?? user.username;
|
||||
const handleSendMessage = async () => {
|
||||
try {
|
||||
const channel = await api.dm.create({ userId: user.id });
|
||||
addDmChannel(channel);
|
||||
useUIStore.getState().setShowDms(true);
|
||||
onClose();
|
||||
navigate(`/channels/@me/${channel.id}`);
|
||||
}
|
||||
catch (err) {
|
||||
console.error('Failed to create DM channel:', err);
|
||||
}
|
||||
};
|
||||
return (_jsxs("div", { className: "fixed z-50 w-[300px] bg-discord-bg-floating rounded-[8px] shadow-elevation-high overflow-hidden animate-fade-in select-none", style: position ? { top: position.top, left: position.left } : { top: '50%', left: '50%', transform: 'translate(-50%, -50%)' }, children: [_jsx("div", { className: "h-[60px] bg-discord-blurple" }), _jsxs("div", { className: "px-4 pb-4 relative", children: [_jsx("div", { className: "absolute -top-8 left-4 rounded-full border-[6px] border-discord-bg-floating bg-discord-bg-floating", children: _jsx(Avatar, { src: user.avatar, name: displayName, size: 80, status: user.status }) }), _jsxs("div", { className: "mt-12 bg-discord-bg-tertiary rounded-[8px] p-3", children: [_jsx("div", { className: "text-[20px] font-bold text-discord-text-header leading-tight mb-1", children: displayName }), _jsxs("div", { className: "text-[14px] text-discord-text-normal font-medium mb-3", children: ["@", user.username] }), _jsx("div", { className: "w-full h-[1px] bg-discord-modifier-accent mb-3" }), _jsxs("div", { className: "mb-3", children: [_jsx("div", { className: "text-[12px] font-bold text-discord-text-header uppercase mb-1", children: "Opencord Member Since" }), _jsx("div", { className: "text-[12px] text-discord-text-normal font-medium", children: new Date(user.createdAt).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' }) })] }), user.customStatus && (_jsxs("div", { className: "mb-3", children: [_jsx("div", { className: "text-[12px] font-bold text-discord-text-header uppercase mb-1", children: "Status" }), _jsx("div", { className: "text-[14px] text-discord-text-normal", children: user.customStatus })] }))] })] }), _jsx("div", { className: "px-4 pb-4", children: _jsx("button", { onClick: handleSendMessage, className: "w-full py-2 bg-discord-blurple hover:bg-discord-blurple-hover text-white text-[14px] font-medium rounded-[4px] transition-colors", children: "Send Message" }) })] }));
|
||||
}
|
||||
Reference in New Issue
Block a user