Skip to content
Merged
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
2 changes: 1 addition & 1 deletion backend/src/main/resources/application.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
app:
version: "0.1.5-alpha"
version: "0.1.6-alpha"

spring:
application:
Expand Down
421 changes: 289 additions & 132 deletions frontend/src/components/DashboardView.tsx

Large diffs are not rendered by default.

48 changes: 32 additions & 16 deletions frontend/src/components/HackerLogo.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,32 @@
display: inline-flex;
align-items: center;
justify-content: center;
transform: translateZ(0);
-webkit-transform: translateZ(0);
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
}

.organicAura {
position: absolute;
width: 150%;
height: 150%;
left: -25%;
top: -25%;
width: 140%;
height: 140%;
left: -20%;
top: -20%;
border-radius: 50%;
background: radial-gradient(
ellipse at center,
rgba(16, 185, 129, 0.3) 0%,
rgba(16, 185, 129, 0.1) 50%,
rgba(16, 185, 129, 0) 75%
);
filter: blur(14px);
filter: blur(12px);
-webkit-filter: blur(12px);
pointer-events: none;
z-index: 0;
animation: organic-pulse 3s ease-in-out infinite alternate;
transform: translateZ(0);
-webkit-transform: translateZ(0);
}

:global(:root[data-theme="light"]) .organicAura {
Expand All @@ -36,11 +43,11 @@
@keyframes organic-pulse {
0% {
opacity: 0.5;
transform: scale(0.9) rotate(0deg);
transform: scale(0.9) rotate(0deg) translateZ(0);
}
100% {
opacity: 0.95;
transform: scale(1.15) rotate(5deg);
transform: scale(1.1) rotate(4deg) translateZ(0);
}
}

Expand All @@ -50,12 +57,14 @@
pointer-events: none;
overflow: visible;
z-index: 10;
transform: translateZ(0);
-webkit-transform: translateZ(0);
}

@keyframes float-particle {
0% {
opacity: 0;
transform: translateY(8px) scale(0.85);
transform: translateY(8px) scale(0.85) translateZ(0);
}
25% {
opacity: 0.9;
Expand All @@ -65,7 +74,7 @@
}
100% {
opacity: 0;
transform: translateY(-32px) scale(1.1);
transform: translateY(-32px) scale(1.1) translateZ(0);
}
}

Expand All @@ -77,31 +86,38 @@
pointer-events: none;
user-select: none;
animation: float-particle 3s ease-in-out infinite;
filter: drop-shadow(0 0 5px rgba(16, 185, 129, 0.8));
filter: drop-shadow(0 0 4px rgba(16, 185, 129, 0.7));
-webkit-filter: drop-shadow(0 0 4px rgba(16, 185, 129, 0.7));
will-change: transform, opacity;
transform: translateZ(0);
-webkit-transform: translateZ(0);
}


:global(:root[data-theme="light"]) .cssParticle {
color: #059669;
filter: drop-shadow(0 0 4px rgba(5, 150, 105, 0.7));
filter: drop-shadow(0 0 4px rgba(5, 150, 105, 0.6));
-webkit-filter: drop-shadow(0 0 4px rgba(5, 150, 105, 0.6));
}

.logoSvg {
position: relative;
z-index: 1;
filter: none !important;
transition: color 0.3s ease;
transform: translateZ(0);
-webkit-transform: translateZ(0);
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
}

.greenLogo {
color: #10b981 !important;
filter: drop-shadow(0 0 5px rgba(16, 185, 129, 0.8))
drop-shadow(0 0 12px rgba(16, 185, 129, 0.5)) !important;
filter: drop-shadow(0 0 6px rgba(16, 185, 129, 0.6)) !important;
-webkit-filter: drop-shadow(0 0 6px rgba(16, 185, 129, 0.6)) !important;
}

:global(:root[data-theme="light"]) .greenLogo {
color: #059669 !important;
filter: drop-shadow(0 0 4px rgba(5, 150, 105, 0.7))
drop-shadow(0 0 10px rgba(5, 150, 105, 0.4)) !important;
filter: drop-shadow(0 0 5px rgba(5, 150, 105, 0.6)) !important;
-webkit-filter: drop-shadow(0 0 5px rgba(5, 150, 105, 0.6)) !important;
}
27 changes: 16 additions & 11 deletions frontend/src/components/ProjectDetailView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,17 @@ import { LinksWorkspace } from "~features/links/components/LinksWorkspace";
import styles from "../routes/projects.$projectId.module.css";
import { getIconComponent } from "../utils/icons";

import { getRouteApi } from "@tanstack/react-router";
import type { ProjectTabType } from "../routes/projects.$projectId";

const routeApi = getRouteApi("/projects/$projectId");

export const ProjectDetailView: React.FC = () => {
const { projectId } = useParamsHelper();
const { close: closeSidebar } = useSidebar();
const search = routeApi.useSearch();
const navigate = routeApi.useNavigate();


// Auto-close sidebar when entering a project
useEffect(() => {
Expand All @@ -32,21 +40,17 @@ export const ProjectDetailView: React.FC = () => {
(p) => p.status === "OPEN" || p.status === "WORKING_ON"
).length;



// Workspace sub-navigation state
const [activeTab, setActiveTab] = useState<
"snippets" | "problems" | "credentials" | "notes" | "links"
>("snippets");
// Workspace sub-navigation state derived directly from URL search params
const activeTab = search.tab || "snippets";

const [isTagsManagerOpen, setIsTagsManagerOpen] = useState(false);

const handleTabChange = (
tab: "snippets" | "problems" | "credentials" | "notes" | "links"
) => {
setActiveTab(tab);
const handleTabChange = (tab: ProjectTabType) => {
navigate({ search: { tab }, replace: true });
};



const projectIcon = getIconComponent(project?.icon);

return (
Expand Down Expand Up @@ -158,11 +162,12 @@ export const ProjectDetailView: React.FC = () => {
<CredentialsWorkspace
projectId={projectId}
isActive={activeTab === "credentials"}
onNavigateTab={setActiveTab}
onNavigateTab={handleTabChange}
onOpenManageTagsModal={() => setIsTagsManagerOpen(true)}
/>
)}


{activeTab === "notes" && (
<NotesWorkspace
projectId={projectId}
Expand Down
23 changes: 17 additions & 6 deletions frontend/src/components/TagManagerSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ interface TagManagerSectionProps {
projectId: string;
onOpenManageTagsModal: () => void;
title?: string;
noBorder?: boolean;
}

export const TagManagerSection: React.FC<TagManagerSectionProps> = ({
Expand All @@ -26,6 +27,7 @@ export const TagManagerSection: React.FC<TagManagerSectionProps> = ({
projectId,
onOpenManageTagsModal,
title = "Associated Tags",
noBorder = false,
}) => {
const { data: tagsData = [] } = useTagsQuery(projectId);
const createTagMutation = useCreateTagMutation(projectId);
Expand Down Expand Up @@ -108,11 +110,14 @@ export const TagManagerSection: React.FC<TagManagerSectionProps> = ({
);

return (
<div className={styles.tagSection}>
<div className={styles.tagHeader}>
<Icons.Tag size={12} className="text-muted-foreground" />
<span className={styles.tagSectionTitle}>{title}</span>
</div>
<div className={`${styles.tagSection} ${noBorder ? styles.tagSectionNoBorder : ""}`}>
{title && title.trim() !== "" && (
<div className={styles.tagHeader}>
<Icons.Tag size={12} className="text-muted-foreground" />
<span className={styles.tagSectionTitle}>{title}</span>
</div>
)}

<div className={styles.tagList}>
{itemTags.map((tag) => (
<span key={tag.id} className={styles.tagPill}>
Expand Down Expand Up @@ -143,17 +148,23 @@ export const TagManagerSection: React.FC<TagManagerSectionProps> = ({
</button>

{isPopoverOpen && (
<div className={styles.tagPopover}>
<div
className={styles.tagPopover}
onClick={(e) => e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()}
>
<div className={styles.popoverHeader}>
<input
type="text"
placeholder="Filter/create tag..."
className={styles.tagSearchInput}
value={tagSearchQuery}
onChange={(e) => setTagSearchQuery(e.target.value)}
onKeyDown={(e) => e.stopPropagation()}
autoFocus
/>
</div>

<div className={styles.popoverList}>
{unassociatedTags.map((t) => (
<button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,8 +323,50 @@ export const CredentialDetailModal: React.FC<CredentialDetailModalProps> = ({
</div>
);
})()}


{/* Generic fallback for any unlisted or custom keys in payload */}
{Object.entries(payload)
.filter(
([key]) =>
![
"username",
"user",
"email",
"password",
"pass",
"secret",
"apiKey",
"api_key",
"token",
"key",
"rawTextContent",
"rawText",
"content",
"text",
].includes(key)
)
.map(([key, val]) => (
<div key={key} className={styles.secretField}>
<div className={styles.labelRow}>
<span className={styles.label}>{key}</span>
</div>
<div className={styles.valueRow}>
<span className={styles.valueText}>{val}</span>
<button
type="button"
className={styles.actionBtn}
onClick={() => copyToClipboard(val, key)}
title={`Copy ${key}`}
>
<Copy size={14} />
</button>
</div>
</div>
))}
</div>


{/* Metadata Section */}
<div className={styles.metaGroup}>
{cred.relatedUrl && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,11 +207,13 @@ export const CredentialsWorkspace: React.FC<CredentialsWorkspaceProps> = ({
className={styles.credentialCard}
onClick={() => setViewingCredentialId(cred.id)}
onKeyDown={(e) => {
if (e.target !== e.currentTarget) return;
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
setViewingCredentialId(cred.id);
}
}}

>

<div className={styles.credentialCardHeader}>
Expand All @@ -236,9 +238,22 @@ export const CredentialsWorkspace: React.FC<CredentialsWorkspaceProps> = ({
</a>
)}
</div>
<div
className={styles.credentialUnlockHint}
title="Click card to unlock vault payload"
>
<Icons.Lock size={13} />
</div>
</div>

{cred.notes && (
<p className={styles.credentialNotes}>{cred.notes}</p>
)}



<div className={styles.credentialCardFooter}>

<div onClick={(e) => e.stopPropagation()}>
<TagManagerSection
itemId={cred.id}
Expand All @@ -247,6 +262,7 @@ export const CredentialsWorkspace: React.FC<CredentialsWorkspaceProps> = ({
projectId={projectId}
onOpenManageTagsModal={onOpenManageTagsModal}
title=""
noBorder={true}
/>
</div>

Expand Down
9 changes: 8 additions & 1 deletion frontend/src/features/links/components/LinksWorkspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,15 @@ export const LinksWorkspace: React.FC<LinksWorkspaceProps> = ({
>
<div className={styles.snippetItemHeader}>
<span className={styles.snippetItemTitle}>{l.title}</span>
<Icons.ExternalLink size={12} className="text-muted-foreground shrink-0" />
<div className="flex items-center gap-1.5 shrink-0 ml-auto pt-0.5">
<span className="text-[10px] text-muted-foreground font-mono">
{new Date(l.createdAt).toLocaleDateString()}
</span>
<Icons.ExternalLink size={12} className="text-muted-foreground" />
</div>
</div>


<span className="text-[10px] text-muted-foreground font-mono truncate max-w-[220px] block">
{l.url}
</span>
Expand Down
Loading