Compare commits

...

5 Commits

Author SHA1 Message Date
Henry
8708c97937 Merge branch 'main' into feat/laka-257 2025-11-30 23:00:47 +00:00
Henry
042b57bfaa Update readme 2025-11-30 22:58:04 +00:00
Henry
c6a26ee2ed feat: add env var to allow invalid SSL certs 2025-11-30 22:56:49 +00:00
Henry
d3c8cbd6c0 fix: persist filters when opening card modal (#261) 2025-11-30 22:32:59 +00:00
Henry
6c3dbaefd3 Update README.md 2025-11-30 22:13:44 +00:00
8 changed files with 98 additions and 72 deletions

View File

@@ -17,6 +17,7 @@ SMTP_USER=
SMTP_PASSWORD= SMTP_PASSWORD=
EMAIL_FROM= # e.g. "Kan <hello@mail.kan.bn>" EMAIL_FROM= # e.g. "Kan <hello@mail.kan.bn>"
SMTP_SECURE= # set to "false" to use port 587 SMTP_SECURE= # set to "false" to use port 587
SMTP_REJECT_UNAUTHORIZED= # set to "false" to accept invalid certs
# Switch email features off entirely (optional) # Switch email features off entirely (optional)
NEXT_PUBLIC_DISABLE_EMAIL= NEXT_PUBLIC_DISABLE_EMAIL=

View File

@@ -34,7 +34,7 @@ See our [roadmap](https://kan.bn/kan/roadmap) for upcoming features.
## Screenshot 👁️ ## Screenshot 👁️
<img width="1507" alt="hero-dark" src="https://github.com/user-attachments/assets/5f7b6ad3-f31d-4b45-93dc-0132b3f2afd4" /> <img width="1507" alt="hero-dark" src="https://github.com/user-attachments/assets/8490104a-cd5d-49de-afc2-152fd8a93119" />
## Made With 🛠️ ## Made With 🛠️
@@ -139,7 +139,7 @@ pnpm dev
## Environment Variables 🔐 ## Environment Variables 🔐
| Variable | Description | Required | Example | | Variable | Description | Required | Example |
| ----------------------------------------- | -------------------------------------------------------- | ------------------------ | ----------------------------------------------------------- | | ----------------------------------------- | --------------------------------------------------------- | ------------------------ | ----------------------------------------------------------- |
| `POSTGRES_URL` | PostgreSQL connection URL | To use external database | `postgres://user:pass@localhost:5432/db` | | `POSTGRES_URL` | PostgreSQL connection URL | To use external database | `postgres://user:pass@localhost:5432/db` |
| `EMAIL_FROM` | Sender email address | For Email | `"Kan <hello@mail.kan.bn>"` | | `EMAIL_FROM` | Sender email address | For Email | `"Kan <hello@mail.kan.bn>"` |
| `SMTP_HOST` | SMTP server hostname | For Email | `smtp.resend.com` | | `SMTP_HOST` | SMTP server hostname | For Email | `smtp.resend.com` |
@@ -147,6 +147,7 @@ pnpm dev
| `SMTP_USER` | SMTP username/email | No | `resend` | | `SMTP_USER` | SMTP username/email | No | `resend` |
| `SMTP_PASSWORD` | SMTP password/token | No | `re_xxxx` | | `SMTP_PASSWORD` | SMTP password/token | No | `re_xxxx` |
| `SMTP_SECURE` | Use secure SMTP connection (defaults to true if not set) | For Email | `true` | | `SMTP_SECURE` | Use secure SMTP connection (defaults to true if not set) | For Email | `true` |
| `SMTP_REJECT_UNAUTHORIZED` | Reject invalid certificates (defaults to true if not set) | For Email | `false` |
| `NEXT_PUBLIC_DISABLE_EMAIL` | To disable all email features | For Email | `true` | | `NEXT_PUBLIC_DISABLE_EMAIL` | To disable all email features | For Email | `true` |
| `NEXT_PUBLIC_BASE_URL` | Base URL of your installation | Yes | `http://localhost:3000` | | `NEXT_PUBLIC_BASE_URL` | Base URL of your installation | Yes | `http://localhost:3000` |
| `BETTER_AUTH_SECRET` | Auth encryption secret | Yes | Random 32+ char string | | `BETTER_AUTH_SECRET` | Auth encryption secret | Yes | Random 32+ char string |
@@ -170,7 +171,7 @@ pnpm dev
| `NEXT_PUBLIC_STORAGE_URL` | Storage service URL | For file uploads | `https://storage.kanbn.com` | | `NEXT_PUBLIC_STORAGE_URL` | Storage service URL | For file uploads | `https://storage.kanbn.com` |
| `NEXT_PUBLIC_STORAGE_DOMAIN` | Storage domain name | For file uploads | `kanbn.com` | | `NEXT_PUBLIC_STORAGE_DOMAIN` | Storage domain name | For file uploads | `kanbn.com` |
| `NEXT_PUBLIC_AVATAR_BUCKET_NAME` | S3 bucket name for avatars | For file uploads | `avatars` | | `NEXT_PUBLIC_AVATAR_BUCKET_NAME` | S3 bucket name for avatars | For file uploads | `avatars` |
| `NEXT_PUBLIC_ATTATCHMENTS_BUCKET_NAME` | S3 bucket name for attatchments | For file uploads | `attatchments` | | `NEXT_PUBLIC_ATTACHMENTS_BUCKET_NAME` | S3 bucket name for attachments | For file uploads | `attachments` |
| `NEXT_PUBLIC_ALLOW_CREDENTIALS` | Allow email & password login | For authentication | `true` | | `NEXT_PUBLIC_ALLOW_CREDENTIALS` | Allow email & password login | For authentication | `true` |
| `NEXT_PUBLIC_DISABLE_SIGN_UP` | Disable sign up | For authentication | `false` | | `NEXT_PUBLIC_DISABLE_SIGN_UP` | Disable sign up | For authentication | `false` |
| `NEXT_PUBLIC_WHITE_LABEL_HIDE_POWERED_BY` | Hide “Powered by kan.bn” on public boards (self-host) | For white labelling | `true` | | `NEXT_PUBLIC_WHITE_LABEL_HIDE_POWERED_BY` | Hide “Powered by kan.bn” on public boards (self-host) | For white labelling | `true` |

View File

@@ -67,22 +67,23 @@ export function CardModal({
<div className="flex w-full items-center justify-between"> <div className="flex w-full items-center justify-between">
<button <button
className="absolute right-[2rem] top-[2rem] rounded p-1 hover:bg-light-300 focus:outline-none dark:hover:bg-dark-300" className="absolute right-[2rem] top-[2rem] rounded p-1 hover:bg-light-300 focus:outline-none dark:hover:bg-dark-300"
onClick={async (e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
closeModal(); closeModal();
setTimeout(async () => { setTimeout(() => {
try { void router.replace(
await router.replace(
`/${workspaceSlug}/${boardSlug}`,
undefined,
{ {
shallow: true, pathname: router.pathname,
query: {
...router.query,
workspaceSlug,
boardSlug: [boardSlug],
}, },
},
undefined,
{ shallow: true },
); );
} catch (error) {
console.error(error);
}
}, 400); }, 400);
}} }}
> >

View File

@@ -76,7 +76,8 @@ export default function PublicBoardView() {
); );
}; };
const splitPath = router.asPath.split("/"); const pathWithoutQuery = router.asPath.split("?")[0];
const splitPath = pathWithoutQuery.split("/");
const cardPublicId = splitPath.length > 3 ? splitPath[3] : null; const cardPublicId = splitPath.length > 3 ? splitPath[3] : null;
useEffect(() => { useEffect(() => {
@@ -172,10 +173,18 @@ export default function PublicBoardView() {
</span> </span>
</div> </div>
<div className="scrollbar-track-rounded-[4px] scrollbar-thumb-rounded-[4px] scrollbar-w-[8px] z-10 h-full max-h-[calc(100vh-265px)] min-h-[2rem] overflow-y-auto pr-1 scrollbar dark:scrollbar-track-dark-100 dark:scrollbar-thumb-dark-600"> <div className="scrollbar-track-rounded-[4px] scrollbar-thumb-rounded-[4px] scrollbar-w-[8px] z-10 h-full max-h-[calc(100vh-265px)] min-h-[2rem] overflow-y-auto pr-1 scrollbar dark:scrollbar-track-dark-100 dark:scrollbar-thumb-dark-600">
{list.cards.map((card) => ( {list.cards.map((card) => {
return (
<Link <Link
key={card.publicId} key={card.publicId}
href={`/${data.workspace.slug}/${data.slug}/${card.publicId}`} href={{
pathname: router.pathname,
query: {
...router.query,
workspaceSlug: data.workspace.slug,
boardSlug: [data.slug, card.publicId],
},
}}
className={`mb-2 flex !cursor-pointer flex-col`} className={`mb-2 flex !cursor-pointer flex-col`}
shallow={true} shallow={true}
onClick={() => { onClick={() => {
@@ -192,7 +201,8 @@ export default function PublicBoardView() {
attachments={card.attachments} attachments={card.attachments}
/> />
</Link> </Link>
))} );
})}
</div> </div>
</div> </div>
))} ))}

View File

@@ -35,6 +35,7 @@ services:
- SMTP_PASSWORD=${SMTP_PASSWORD} - SMTP_PASSWORD=${SMTP_PASSWORD}
- SMTP_SECURE=${SMTP_SECURE} - SMTP_SECURE=${SMTP_SECURE}
- EMAIL_FROM=${EMAIL_FROM} - EMAIL_FROM=${EMAIL_FROM}
- SMTP_REJECT_UNAUTHORIZED=${SMTP_REJECT_UNAUTHORIZED}
# Notifications # Notifications
- NOVU_API_KEY=${NOVU_API_KEY} - NOVU_API_KEY=${NOVU_API_KEY}

View File

@@ -24,6 +24,7 @@ services:
- SMTP_USER=${SMTP_USER} - SMTP_USER=${SMTP_USER}
- SMTP_PASSWORD=${SMTP_PASSWORD} - SMTP_PASSWORD=${SMTP_PASSWORD}
- EMAIL_FROM=${EMAIL_FROM} - EMAIL_FROM=${EMAIL_FROM}
- SMTP_REJECT_UNAUTHORIZED=${SMTP_REJECT_UNAUTHORIZED}
# Disable email features entirely (optional) # Disable email features entirely (optional)
- NEXT_PUBLIC_DISABLE_EMAIL=${NEXT_PUBLIC_DISABLE_EMAIL} - NEXT_PUBLIC_DISABLE_EMAIL=${NEXT_PUBLIC_DISABLE_EMAIL}

View File

@@ -16,7 +16,17 @@ const emailTemplates: Record<Templates, React.FC> = {
const transporter = nodemailer.createTransport({ const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST, host: process.env.SMTP_HOST,
port: Number(process.env.SMTP_PORT), port: Number(process.env.SMTP_PORT),
secure: process.env.SMTP_SECURE !== "false", secure:
process.env.SMTP_SECURE === undefined
? true
: process.env.SMTP_SECURE?.toLowerCase() === "true",
tls: {
// do not fail on invalid certs
rejectUnauthorized:
process.env.SMTP_REJECT_UNAUTHORIZED === undefined
? true
: process.env.SMTP_REJECT_UNAUTHORIZED?.toLowerCase() === "true",
},
...(process.env.SMTP_USER && ...(process.env.SMTP_USER &&
process.env.SMTP_PASSWORD && { process.env.SMTP_PASSWORD && {
auth: { auth: {

View File

@@ -96,6 +96,7 @@
"SMTP_USER", "SMTP_USER",
"SMTP_PASSWORD", "SMTP_PASSWORD",
"SMTP_SECURE", "SMTP_SECURE",
"SMTP_REJECT_UNAUTHORIZED",
"NEXT_PUBLIC_KAN_ENV", "NEXT_PUBLIC_KAN_ENV",
"NEXT_PUBLIC_WHITE_LABEL_HIDE_POWERED_BY", "NEXT_PUBLIC_WHITE_LABEL_HIDE_POWERED_BY",
"STRIPE_SECRET_KEY", "STRIPE_SECRET_KEY",