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
5,741 changes: 3,570 additions & 2,171 deletions package-lock.json

Large diffs are not rendered by default.

46 changes: 23 additions & 23 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,49 +22,49 @@
},
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@mui/icons-material": "^6.4.6",
"@mui/material": "^6.4.6",
"@mui/x-date-pickers": "^7.27.1",
"@emotion/styled": "^11.14.1",
"@mui/icons-material": "^6.5.0",
"@mui/material": "^6.5.0",
"@mui/x-date-pickers": "^7.29.4",
"@nextui-org/react": "^2.6.11",
"@tailwindcss/postcss": "^4.0.9",
"@tanstack/react-query": "^5.66.11",
"@widgetbot/react-embed": "^1.9.0",
"@tailwindcss/postcss": "^4.3.1",
"@tanstack/react-query": "^5.101.2",
"@widgetbot/react-embed": "^1.11.3",
"aos": "^2.3.4",
"apexcharts": "^4.5.0",
"apexcharts": "^4.7.0",
"autoprefixer": "10.4.20",
"axios": "^1.8.1",
"date-fns": "^4.1.0",
"discord-api-types": "^0.38.0",
"axios": "^1.18.1",
"date-fns": "^4.4.0",
"discord-api-types": "^0.38.49",
"kofi-button": "^1.1.1",
"material-react-table": "^3.2.1",
"next": "^15.2.0",
"next": "^15.5.19",
"particles-bg": "^2.5.5",
"react": "^18.3.1",
"react-apexcharts": "^1.7.0",
"react-apexcharts": "^1.9.0",
"react-countup": "^6.5.3",
"react-dom": "^18.3.1",
"swiper": "^11.2.4",
"swiper": "^11.2.10",
"tailwindcss": "^4.0.9",
"tripsit_drug_db": "github:tripsit/drugs",
"typescript": "^5.8.2"
"typescript": "^5.9.3"
},
"devDependencies": {
"@types/aos": "^3.0.7",
"@types/node": "^22.13.8",
"@types/react": "^18.3.23",
"@types/aos": "^3.0.8",
"@types/node": "^22.20.0",
"@types/react": "^18.3.31",
"@types/react-dom": "^18.3.7",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"eslint": "^8.57.1",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-airbnb-typescript": "^17.1.0",
"eslint-config-next": "^14.2.24",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.31.0",
"eslint-config-next": "^14.2.35",
Comment thread
sympact06 marked this conversation as resolved.
"eslint-config-prettier": "^9.1.2",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-jest": "^27.9.0",
"eslint-plugin-prettier": "^5.2.3",
"eslint-plugin-prettier": "^5.5.6",
"eslint-plugin-sonarjs": "^0.25.1",
"prettier": "^3.5.2"
"prettier": "^3.9.1"
}
}
Binary file added public/assets/fonts/FuturaStd-Bold.otf
Binary file not shown.
Binary file added public/assets/fonts/letter-gothic.ttf
Binary file not shown.
3 changes: 2 additions & 1 deletion src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Image from "next/image";
import Link from "next/link";
import { Tooltip } from "@mui/material";
import { useState } from "react";
import logo from "../../public/assets/img/logo.png";
Expand Down Expand Up @@ -72,7 +73,7 @@ function Header() {
<a href={`https://${domain}/factsheets`}>Factsheets</a>
</li>
<li>
<a href={`https://combo.${domain}`}>Comboapp</a>
<Link href="/combo">Comboapp</Link>
</li>
<li>
<a href={`https://${domain}#faq`}>
Expand Down
35 changes: 35 additions & 0 deletions src/components/comboChecker/ComboCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { RISK, type RiskKey } from "./comboData";
import styles from "./comboChecker.module.css";

interface ComboCardProps {
title: string;
risk: RiskKey;
note: string;
showNote: boolean;
delay: string;
}

function ComboCard({ title, risk, note, showNote, delay }: ComboCardProps) {
const r = RISK[risk];
return (
<div className={styles.card} style={{ animationDelay: delay }}>
<div className={styles.cardHead}>
<span className={styles.cardBadge} style={{ background: r.badge }}>
{r.glyph}
</span>
<div className={styles.cardTitleWrap}>
<div className={`${styles.cardTitle} ${styles.disp}`}>{title}</div>
<div
className={`${styles.cardLabel} ${styles.mono}`}
style={{ color: r.badge }}
>
{r.label}
</div>
</div>
</div>
{showNote && note && <p className={styles.cardNote}>{note}</p>}
</div>
);
}

export default ComboCard;
121 changes: 121 additions & 0 deletions src/components/comboChecker/ComboChecker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { useState, type CSSProperties } from "react";
import Image from "next/image";
import Link from "next/link";
import logo from "../../../public/assets/img/logo.png";
import { SUBSTANCES } from "./comboData";
import Legend from "./Legend";
import SubstanceSelector from "./SubstanceSelector";
import ComboResults from "./ComboResults";
import styles from "./comboChecker.module.css";

interface ComboCheckerProps {
/** Accent color. Design options: #2487ce, #683394, #47aeff, #11dbcf. */
accent?: string;
/** Show the explanation note on each result card. */
showExplanations?: boolean;
/** Sort result cards by severity (most dangerous first). */
sortBySeverity?: boolean;
}

function ComboChecker({
accent = "#2487ce",
showExplanations = true,
sortBySeverity = true,
}: ComboCheckerProps) {
const domain = process.env.NEXT_PUBLIC_DNS_DOMAIN || "tripsit.me";
const [selected, setSelected] = useState<string[]>([]);
const [query, setQuery] = useState("");

const toggle = (name: string) =>
setSelected((prev) =>
prev.includes(name) ? prev.filter((n) => n !== name) : prev.concat(name),
);

return (
<div
className={styles.root}
style={{ "--cc-accent": accent } as CSSProperties}
>
<nav className={styles.nav}>
<a href={`https://${domain}`} className={styles.brand}>
<Image
src={logo}
alt="TripSit"
className={styles.brandLogo}
priority
/>
</a>
<div className={styles.navLinks}>
<a href={`https://${domain}#about`} className={styles.navLink}>
About
</a>
<Link href="/factsheets" className={styles.navLink}>
Factsheets
</Link>
<Link href="/webchat" className={styles.navLink}>
Chat
</Link>
<Link
href="/combo"
className={`${styles.navLink} ${styles.navLinkActive}`}
>
Tools
</Link>
</div>
</nav>

<main className={styles.main}>
<header>
<h1 className={`${styles.title} ${styles.disp}`}>
Drug Combinations
</h1>
<p className={styles.subtitle}>
Check how two or more substances interact before you mix. Pick
substances below. This is a quick-reference guide, not medical
advice.
</p>
</header>

<Legend />

<SubstanceSelector
substances={SUBSTANCES}
selected={selected}
query={query}
onToggle={toggle}
onQuery={setQuery}
onClearAll={() => setSelected([])}
/>

<p
className={`${styles.sectionLabel} ${styles.sectionLabelResults} ${styles.disp}`}
>
Combinations
</p>
<ComboResults
selected={selected}
accent={accent}
showExplanations={showExplanations}
sortBySeverity={sortBySeverity}
/>

<div className={styles.disclaimer}>
This tool, like the combo chart, is a quick-reference guide.{" "}
<strong>Additional research MUST always be done</strong>. When in
doubt, don&apos;t mix; start low and go slow.
</div>
</main>

<footer className={styles.footer}>
<div className={styles.footerInner}>
<Image src={logo} alt="TripSit" className={styles.footerLogo} />
<span className={styles.footerCopy}>
© Copyright TripSit. All Rights Reserved.
</span>
</div>
</footer>
</div>
);
}

export default ComboChecker;
90 changes: 90 additions & 0 deletions src/components/comboChecker/ComboResults.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { useMemo } from "react";
import { accentTone, getCombo, RANK, type RiskKey } from "./comboData";
import ComboCard from "./ComboCard";
import styles from "./comboChecker.module.css";

interface ComboResultsProps {
selected: string[];
accent: string;
showExplanations: boolean;
sortBySeverity: boolean;
}

interface Card {
key: string;
title: string;
risk: RiskKey;
note: string;
}

function ComboResults({
selected,
accent,
showExplanations,
sortBySeverity,
}: ComboResultsProps) {
const cards = useMemo<Card[]>(() => {
const out: (Card & { rank: number })[] = [];
for (let i = 0; i < selected.length; i += 1) {
for (let j = i + 1; j < selected.length; j += 1) {
const a = selected[i];
const b = selected[j];
const { risk, note } = getCombo(a, b);
out.push({
key: `${a}|${b}`,
title: `${a} + ${b}`,
risk,
note,
rank: RANK[risk],
});
}
}
if (sortBySeverity) out.sort((x, y) => y.rank - x.rank);
return out;
}, [selected, sortBySeverity]);

if (selected.length < 2) {
const tone = accentTone(accent);
const emptyText =
selected.length === 0
? "Select two or more substances above to see how they interact."
: `Select at least one more substance to compare with ${selected[0]}.`;
return (
<div className={styles.empty}>
<div
className={styles.emptyIcon}
style={{ background: tone.container }}
>
<svg
width="34"
height="24"
viewBox="0 0 40 28"
fill="none"
aria-hidden="true"
>
<circle cx="15" cy="14" r="11" stroke={accent} strokeWidth="2.4" />
<circle cx="25" cy="14" r="11" stroke={accent} strokeWidth="2.4" />
</svg>
</div>
<p className={styles.emptyText}>{emptyText}</p>
</div>
);
}

return (
<div className={styles.grid}>
{cards.map((c, i) => (
<ComboCard
key={c.key}
title={c.title}
risk={c.risk}
note={c.note}
showNote={showExplanations}
delay={`${Math.min(i, 8) * 0.035}s`}
/>
))}
</div>
);
}

export default ComboResults;
29 changes: 29 additions & 0 deletions src/components/comboChecker/Legend.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { LEGEND_ORDER, RISK } from "./comboData";
import styles from "./comboChecker.module.css";

function Legend() {
return (
<div className={styles.legend}>
{LEGEND_ORDER.map((key) => {
const r = RISK[key];
return (
<span
key={key}
className={styles.legendItem}
style={{ background: r.cBg, color: r.cText }}
>
<span
className={styles.legendBadge}
style={{ background: r.badge }}
>
{r.glyph}
</span>
{r.label}
</span>
);
})}
</div>
);
}

export default Legend;
Loading
Loading