Compare commits

...

8 Commits

Author SHA1 Message Date
Henry
78e2de90d9 chore: update lang 2025-07-28 22:51:23 +01:00
Henry
adcc4470dc feat: show not found message for public boards 2025-07-28 22:47:53 +01:00
Henry
28d2de4dad fix: update public board lookup to filter by workspace slug 2025-07-28 22:21:52 +01:00
Ridham Khandar
ca1dd92ab4 fix: disable create list and label button when input is empty (#130) 2025-07-28 21:49:24 +01:00
Ridham Khandar
f8827da73a fix: creating a new label when adding a new card resets all fields (#119)
* fix: creating a new label when adding a new card resets all fields

* fix: persist form data and modal stacking without breaking the animations
2025-07-28 21:49:13 +01:00
Henry
babaa9f1b1 feat: setup plunk for transactional/marketing emails 2025-07-28 07:09:30 +01:00
Henry
0cefd17484 fix: prevent member dropdown from being cutoff (#127) 2025-07-26 21:34:05 +01:00
Henry
974a5ff0f3 fix: update board slug availability check to be unique to workspace (#126) 2025-07-26 21:18:26 +01:00
35 changed files with 646 additions and 295 deletions

View File

@@ -308,6 +308,7 @@ checksums:
Theme/singular: 21fe00b7a518089576fb83c08631107a
They%20won't%20be%20able%20to%20access%20this%20workspace./singular: 93b740350fe3430319fbca85349e41d9
This%20action%20can't%20be%20undone./singular: cb222ff89715d8c971e8c25d121e1dbd
This%20board%20is%20private%20or%20does%20not%20exist/singular: a217ff3f04463b4df8c86adb6f83c6bc
This%20board%20URL%20has%20already%20been%20taken/singular: 1d8b40332a031b5b77a3658e48dd51ca
This%20will%20result%20in%20the%20permanent%20deletion%20of%20all%20data%20associated%20with%20this%20workspace./singular: a31141558af793635c1ddd2fa0a33499
This%20will%20result%20in%20the%20permanent%20deletion%20of%20all%20data%20associated%20with%20your%20account./singular: b49224632bd6c3b7f5e462912aeb1081
@@ -358,6 +359,7 @@ checksums:
View%20and%20manage%20your%20billing%20and%20subscription./singular: 7469502543aac6f02149813546d1cb38
View%20docs/singular: 43ce54f27bb1e3f1880b56e03ab38871
View%20our%20roadmap./singular: 1dd7ec80e1635951b1c90d592d1d07dc
View%20workspace/singular: c9e4b789330b08c18ca8db7044409e5f
Visibility/singular: 1ff412f8fcde0f460acbf11153649646
We%20are%20using%20the%20%3C0%3EAGPL-3.0%20license%3C%2F0%3E./singular: 08c5ddd938a2a70f61d684e41e18697a
We're%20just%20getting%20started.%20/singular: 2dfa121e0ff1914e08068308a81973c0

View File

@@ -86,8 +86,10 @@ export default function CheckboxDropdown({
</label>
{handleEdit && (
<button
type="button"
className="invisible ml-auto group-hover:visible"
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
handleEdit(item.key);
}}
@@ -100,8 +102,12 @@ export default function CheckboxDropdown({
))}
{handleCreate && (
<button
type="button"
className="flex w-full items-center rounded-[5px] p-2 px-2 text-[12px] text-dark-900 hover:bg-light-200 dark:hover:bg-dark-300"
onClick={() => handleCreate()}
onClick={(e) => {
e.preventDefault();
handleCreate();
}}
>
<HiMiniPlus size={20} className="pr-1.5" />
{createNewItemLabel}

View File

@@ -15,6 +15,7 @@ import {
ReactRenderer,
useEditor,
} from "@tiptap/react";
import { useEffect } from "react";
import StarterKit from "@tiptap/starter-kit";
import Suggestion from "@tiptap/suggestion";
import { forwardRef, useImperativeHandle, useRef, useState } from "react";
@@ -343,9 +344,19 @@ export default function Editor({
editable: !readOnly,
injectCSS: false,
},
[content],
[], // creating the editor only once
);
// this will sync external content changes without re-creating the editor instance
useEffect(() => {
if (!editor) return;
const currentHTML = editor.getHTML();
const safeContent = content ?? "";
if (safeContent !== currentHTML) {
editor.commands.setContent(safeContent, false);
}
}, [content, editor]);
return (
<div ref={containerRef}>
<style jsx global>{`

View File

@@ -63,12 +63,16 @@ export function LabelForm({
);
try {
refetch();
if (!isCreateAnotherEnabled) closeModal();
reset({
name: "",
colour: colours[(currentColourIndex + 1) % colours.length],
isCreateAnotherEnabled,
});
if (!isCreateAnotherEnabled) {
closeModal();
} else {
const newFormState = {
name: "",
colour: colours[(currentColourIndex + 1) % colours.length],
isCreateAnotherEnabled,
};
reset(newFormState);
}
} catch (e) {
console.log(e);
}
@@ -79,10 +83,6 @@ export function LabelForm({
onSuccess: () => {
refetch();
closeModal();
reset({
name: "",
colour: colours[0],
});
},
});
@@ -229,6 +229,9 @@ export function LabelForm({
<Button
type="submit"
isLoading={updateLabel.isPending || createLabel.isPending}
disabled={
!watch("name")
}
>
{isEdit ? t`Update label` : t`Create label`}
</Button>

View File

@@ -7,15 +7,19 @@ interface Props {
children: React.ReactNode;
modalSize?: "sm" | "md" | "lg";
positionFromTop?: "sm" | "md" | "lg";
isVisible?: boolean;
}
const Modal: React.FC<Props> = ({
children,
modalSize = "sm",
positionFromTop = "md",
isVisible,
}) => {
const { isOpen, closeModal } = useModal();
const shouldShow = isVisible !== undefined ? isVisible : isOpen;
const modalSizeMap = {
sm: "max-w-[400px]",
md: "max-w-[550px]",
@@ -29,7 +33,7 @@ const Modal: React.FC<Props> = ({
};
return (
<Transition.Root show={isOpen} as={Fragment}>
<Transition.Root show={shouldShow} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={closeModal}>
<Transition.Child
as={Fragment}

View File

@@ -0,0 +1,48 @@
import { useEffect } from "react";
import { useModal } from "~/providers/modal";
interface UseModalFormStateOptions<T> {
modalType: string;
initialValues: T;
resetOnClose?: boolean;
}
export function useModalFormState<T extends Record<string, any>>({
modalType,
initialValues,
resetOnClose = false,
}: UseModalFormStateOptions<T>) {
const { modalContentType, isOpen, getModalState, setModalState, clearModalState } = useModal();
const isCurrentModal = modalContentType === modalType;
const savedState = getModalState(modalType) as T | undefined;
// get current form state (using the saved values if available, otherwise the initial values)
const formState = savedState || initialValues;
const saveFormState = (state: Partial<T>) => {
if (!isCurrentModal) return;
const currentState = getModalState(modalType) || initialValues;
const newState = { ...currentState, ...state };
setModalState(modalType, newState);
};
const clearFormState = () => {
clearModalState(modalType);
};
useEffect(() => {
if (resetOnClose && !isOpen && savedState) {
clearModalState(modalType);
}
}, [isOpen, resetOnClose, savedState, modalType, clearModalState]);
return {
formState,
saveFormState,
clearFormState,
isCurrentModal,
hasSavedState: !!savedState,
};
}

File diff suppressed because one or more lines are too long

View File

@@ -28,7 +28,7 @@ msgid "{0} | {1}"
msgstr "{0} | {1}"
#. placeholder {0}: labelPublicIds.length
#: src/views/board/components/NewCardForm.tsx:365
#: src/views/board/components/NewCardForm.tsx:392
msgid "{0} labels"
msgstr "{0} Labels"
@@ -66,7 +66,7 @@ msgstr "Karte hinzufügen"
msgid "Add a comment..."
msgstr "Kommentar hinzufügen..."
#: src/components/Editor.tsx:302
#: src/components/Editor.tsx:303
msgid "Add description... (type '/' to open commands)"
msgstr "Beschreibung hinzufügen... (tippe '/' um Befehle zu öffnen)"
@@ -178,7 +178,7 @@ msgstr "Abrechnungsportal"
msgid "Blog Post"
msgstr "Blogbeitrag"
#: src/views/board/index.tsx:241
#: src/views/board/index.tsx:332
msgid "Board"
msgstr "Board"
@@ -190,7 +190,8 @@ msgstr "Der Name des Boards darf 100 Zeichen nicht überschreiten"
msgid "Board name is required"
msgstr "Boardname ist erforderlich"
#: src/views/board/index.tsx:267
#: src/views/public/board/index.tsx:137
#: src/views/board/index.tsx:358
msgid "Board not found"
msgstr "Board nicht gefunden"
@@ -202,7 +203,7 @@ msgstr "Board-URL darf nur Buchstaben, Zahlen und Bindestriche enthalten"
msgid "Board URL cannot exceed 60 characters"
msgstr "Board-URL darf 60 Zeichen nicht überschreiten"
#: src/views/public/board/index.tsx:53
#: src/views/public/board/index.tsx:59
msgid "Board URL copied to clipboard"
msgstr "Board-URL in die Zwischenablage kopiert"
@@ -254,7 +255,7 @@ msgstr "Abbrechen"
msgid "Card not found"
msgstr "Karte nicht gefunden"
#: src/views/board/components/NewCardForm.tsx:254
#: src/views/board/components/NewCardForm.tsx:278
msgid "Card title"
msgstr "Kartentitel"
@@ -347,7 +348,7 @@ msgid "Control who can view and edit your boards."
msgstr "Kontrolliere, wer deine Boards ansehen und bearbeiten kann."
#: src/views/board/components/NewListForm.tsx:140
#: src/views/board/components/NewCardForm.tsx:391
#: src/views/board/components/NewCardForm.tsx:418
#: src/components/LabelForm.tsx:212
msgid "Create another"
msgstr "Weitere erstellen"
@@ -356,15 +357,15 @@ msgstr "Weitere erstellen"
msgid "Create board"
msgstr "Board erstellen"
#: src/views/board/components/NewCardForm.tsx:401
#: src/views/board/components/NewCardForm.tsx:428
msgid "Create card"
msgstr "Karte erstellen"
#: src/components/LabelForm.tsx:233
#: src/components/LabelForm.tsx:236
msgid "Create label"
msgstr "Label erstellen"
#: src/views/board/components/NewListForm.tsx:148
#: src/views/board/components/NewListForm.tsx:152
msgid "Create list"
msgstr "Liste erstellen"
@@ -377,11 +378,11 @@ msgid "Create new key"
msgstr "Neuen Schlüssel erstellen"
#: src/views/card/components/LabelSelector.tsx:98
#: src/views/board/components/NewCardForm.tsx:327
#: src/views/board/components/NewCardForm.tsx:354
msgid "Create new label"
msgstr "Neues Label erstellen"
#: src/views/board/index.tsx:335
#: src/views/board/index.tsx:426
msgid "Create new list"
msgstr "Neue Liste erstellen"
@@ -676,7 +677,7 @@ msgstr "Loslegen"
msgid "Get started by creating a new board"
msgstr "Beginne, indem du ein neues Board erstellst"
#: src/views/board/index.tsx:327
#: src/views/board/index.tsx:418
msgid "Get started by creating a new list"
msgstr "Beginne mit dem Erstellen einer neuen Liste"
@@ -826,7 +827,7 @@ msgid "Keep in mind that this action is irreversible."
msgstr "Beachten Sie, dass diese Aktion nicht rückgängig gemacht werden kann."
#: src/views/card/index.tsx:113
#: src/views/board/components/NewCardForm.tsx:331
#: src/views/board/components/NewCardForm.tsx:358
#: src/views/board/components/Filters.tsx:101
msgid "Labels"
msgstr "Labels"
@@ -855,7 +856,7 @@ msgstr "Lizenz"
msgid "Light"
msgstr "Hell"
#: src/views/public/board/index.tsx:51
#: src/views/public/board/index.tsx:57
msgid "Link copied"
msgstr "Link kopiert"
@@ -893,7 +894,7 @@ msgstr "Mittlere Priorität"
#: src/views/members/index.tsx:161
#: src/views/card/index.tsx:121
#: src/views/board/components/NewCardForm.tsx:290
#: src/views/board/components/NewCardForm.tsx:317
#: src/views/board/components/Filters.tsx:93
#: src/components/SideNavigation.tsx:73
msgid "Members"
@@ -933,7 +934,7 @@ msgstr "Neu"
msgid "New board"
msgstr "Neues Board"
#: src/views/board/components/NewCardForm.tsx:237
#: src/views/board/components/NewCardForm.tsx:261
msgid "New card"
msgstr "Neue Karte"
@@ -945,7 +946,7 @@ msgstr "Neuer Import"
msgid "New label"
msgstr "Neues Label"
#: src/views/board/index.tsx:304
#: src/views/board/index.tsx:395
#: src/views/board/components/NewListForm.tsx:112
msgid "New list"
msgstr "Neue Liste"
@@ -978,7 +979,7 @@ msgstr "Keine Boards gefunden"
msgid "No credit card required"
msgstr "Keine Kreditkarte erforderlich"
#: src/views/board/index.tsx:324
#: src/views/board/index.tsx:415
msgid "No lists"
msgstr "Keine Listen"
@@ -1074,7 +1075,7 @@ msgstr "Bitte wähle eine Datei zum Hochladen aus."
#: src/views/board/components/VisibilityButton.tsx:53
#: src/views/board/components/UpdateBoardSlugForm.tsx:72
#: src/views/board/components/NewListForm.tsx:78
#: src/views/board/components/NewCardForm.tsx:128
#: src/views/board/components/NewCardForm.tsx:145
#: src/components/NewWorkspaceForm.tsx:41
#: src/components/FeedbackModal.tsx:41
msgid "Please try again later, or contact customer support."
@@ -1338,6 +1339,10 @@ msgstr "Sie werden keinen Zugriff mehr auf diesen Workspace haben."
msgid "This action can't be undone."
msgstr "Diese Aktion kann nicht rückgängig gemacht werden."
#: src/views/public/board/index.tsx:140
msgid "This board is private or does not exist"
msgstr "Dieses Board ist privat oder existiert nicht"
#: src/views/board/components/UpdateBoardSlugForm.tsx:135
msgid "This board URL has already been taken"
msgstr "Diese Board-URL ist bereits vergeben"
@@ -1383,7 +1388,7 @@ msgstr "Priorisierung"
msgid "Unable to add comment"
msgstr "Kommentar konnte nicht hinzugefügt werden"
#: src/views/board/components/NewCardForm.tsx:125
#: src/views/board/components/NewCardForm.tsx:142
msgid "Unable to create card"
msgstr "Karte konnte nicht erstellt werden"
@@ -1477,11 +1482,11 @@ msgstr "Unbegrenzte listen"
#: src/views/settings/components/UpdateWorkspaceNameForm.tsx:82
#: src/views/settings/components/UpdateWorkspaceDescriptionForm.tsx:90
#: src/views/settings/components/UpdateDisplayNameForm.tsx:79
#: src/views/board/components/UpdateBoardSlugForm.tsx:173
#: src/views/board/components/UpdateBoardSlugForm.tsx:174
msgid "Update"
msgstr "Aktualisieren"
#: src/components/LabelForm.tsx:233
#: src/components/LabelForm.tsx:236
msgid "Update label"
msgstr "Label aktualisieren"
@@ -1546,6 +1551,10 @@ msgstr "Dokumentation ansehen"
msgid "View our roadmap."
msgstr "Sieh dir unsere roadmap an."
#: src/views/public/board/index.tsx:143
msgid "View workspace"
msgstr "Workspace anzeigen"
#: src/views/board/components/VisibilityButton.tsx:91
msgid "Visibility"
msgstr "Sichtbarkeit"
@@ -1578,7 +1587,7 @@ msgstr "Als Trello 2011 auf den markt kam, beeindruckte es alle mit seiner sorgf
msgid "Why make an open source Trello?"
msgstr "Warum ein open source Trello entwickeln?"
#: src/views/board/index.tsx:241
#: src/views/board/index.tsx:332
msgid "Workspace"
msgstr "Arbeitsbereich"

File diff suppressed because one or more lines are too long

View File

@@ -28,7 +28,7 @@ msgid "{0} | {1}"
msgstr "{0} | {1}"
#. placeholder {0}: labelPublicIds.length
#: src/views/board/components/NewCardForm.tsx:365
#: src/views/board/components/NewCardForm.tsx:392
msgid "{0} labels"
msgstr "{0} labels"
@@ -78,7 +78,7 @@ msgstr "Add a card"
msgid "Add a comment..."
msgstr "Add a comment..."
#: src/components/Editor.tsx:302
#: src/components/Editor.tsx:303
msgid "Add description... (type '/' to open commands)"
msgstr "Add description... (type '/' to open commands)"
@@ -198,7 +198,7 @@ msgstr "Billing portal"
msgid "Blog Post"
msgstr "Blog Post"
#: src/views/board/index.tsx:241
#: src/views/board/index.tsx:332
msgid "Board"
msgstr "Board"
@@ -214,7 +214,8 @@ msgstr "Board name cannot exceed 100 characters"
msgid "Board name is required"
msgstr "Board name is required"
#: src/views/board/index.tsx:267
#: src/views/public/board/index.tsx:137
#: src/views/board/index.tsx:358
msgid "Board not found"
msgstr "Board not found"
@@ -226,7 +227,7 @@ msgstr "Board URL can only contain letters, numbers, and hyphens"
msgid "Board URL cannot exceed 60 characters"
msgstr "Board URL cannot exceed 60 characters"
#: src/views/public/board/index.tsx:53
#: src/views/public/board/index.tsx:59
msgid "Board URL copied to clipboard"
msgstr "Board URL copied to clipboard"
@@ -282,7 +283,7 @@ msgstr "Cancel"
msgid "Card not found"
msgstr "Card not found"
#: src/views/board/components/NewCardForm.tsx:254
#: src/views/board/components/NewCardForm.tsx:278
msgid "Card title"
msgstr "Card title"
@@ -375,7 +376,7 @@ msgid "Control who can view and edit your boards."
msgstr "Control who can view and edit your boards."
#: src/views/board/components/NewListForm.tsx:140
#: src/views/board/components/NewCardForm.tsx:391
#: src/views/board/components/NewCardForm.tsx:418
#: src/components/LabelForm.tsx:212
msgid "Create another"
msgstr "Create another"
@@ -392,15 +393,15 @@ msgstr "Create another"
msgid "Create board"
msgstr "Create board"
#: src/views/board/components/NewCardForm.tsx:401
#: src/views/board/components/NewCardForm.tsx:428
msgid "Create card"
msgstr "Create card"
#: src/components/LabelForm.tsx:233
#: src/components/LabelForm.tsx:236
msgid "Create label"
msgstr "Create label"
#: src/views/board/components/NewListForm.tsx:148
#: src/views/board/components/NewListForm.tsx:152
msgid "Create list"
msgstr "Create list"
@@ -413,11 +414,11 @@ msgid "Create new key"
msgstr "Create new key"
#: src/views/card/components/LabelSelector.tsx:98
#: src/views/board/components/NewCardForm.tsx:327
#: src/views/board/components/NewCardForm.tsx:354
msgid "Create new label"
msgstr "Create new label"
#: src/views/board/index.tsx:335
#: src/views/board/index.tsx:426
msgid "Create new list"
msgstr "Create new list"
@@ -713,7 +714,7 @@ msgstr "Get Started"
msgid "Get started by creating a new board"
msgstr "Get started by creating a new board"
#: src/views/board/index.tsx:327
#: src/views/board/index.tsx:418
msgid "Get started by creating a new list"
msgstr "Get started by creating a new list"
@@ -867,7 +868,7 @@ msgid "Keep in mind that this action is irreversible."
msgstr "Keep in mind that this action is irreversible."
#: src/views/card/index.tsx:113
#: src/views/board/components/NewCardForm.tsx:331
#: src/views/board/components/NewCardForm.tsx:358
#: src/views/board/components/Filters.tsx:101
msgid "Labels"
msgstr "Labels"
@@ -896,7 +897,7 @@ msgstr "License"
msgid "Light"
msgstr "Light"
#: src/views/public/board/index.tsx:51
#: src/views/public/board/index.tsx:57
msgid "Link copied"
msgstr "Link copied"
@@ -934,7 +935,7 @@ msgstr "Medium Priority"
#: src/views/members/index.tsx:161
#: src/views/card/index.tsx:121
#: src/views/board/components/NewCardForm.tsx:290
#: src/views/board/components/NewCardForm.tsx:317
#: src/views/board/components/Filters.tsx:93
#: src/components/SideNavigation.tsx:73
msgid "Members"
@@ -974,7 +975,7 @@ msgstr "New"
msgid "New board"
msgstr "New board"
#: src/views/board/components/NewCardForm.tsx:237
#: src/views/board/components/NewCardForm.tsx:261
msgid "New card"
msgstr "New card"
@@ -986,7 +987,7 @@ msgstr "New import"
msgid "New label"
msgstr "New label"
#: src/views/board/index.tsx:304
#: src/views/board/index.tsx:395
#: src/views/board/components/NewListForm.tsx:112
msgid "New list"
msgstr "New list"
@@ -1023,7 +1024,7 @@ msgstr "No boards found"
msgid "No credit card required"
msgstr "No credit card required"
#: src/views/board/index.tsx:324
#: src/views/board/index.tsx:415
msgid "No lists"
msgstr "No lists"
@@ -1119,7 +1120,7 @@ msgstr "Please select a file to upload."
#: src/views/board/components/VisibilityButton.tsx:53
#: src/views/board/components/UpdateBoardSlugForm.tsx:72
#: src/views/board/components/NewListForm.tsx:78
#: src/views/board/components/NewCardForm.tsx:128
#: src/views/board/components/NewCardForm.tsx:145
#: src/components/NewWorkspaceForm.tsx:41
#: src/components/FeedbackModal.tsx:41
msgid "Please try again later, or contact customer support."
@@ -1387,6 +1388,10 @@ msgstr "They won't be able to access this workspace."
msgid "This action can't be undone."
msgstr "This action can't be undone."
#: src/views/public/board/index.tsx:140
msgid "This board is private or does not exist"
msgstr "This board is private or does not exist"
#: src/views/board/components/UpdateBoardSlugForm.tsx:135
msgid "This board URL has already been taken"
msgstr "This board URL has already been taken"
@@ -1436,7 +1441,7 @@ msgstr "Triaging"
msgid "Unable to add comment"
msgstr "Unable to add comment"
#: src/views/board/components/NewCardForm.tsx:125
#: src/views/board/components/NewCardForm.tsx:142
msgid "Unable to create card"
msgstr "Unable to create card"
@@ -1530,11 +1535,11 @@ msgstr "Unlimited lists"
#: src/views/settings/components/UpdateWorkspaceNameForm.tsx:82
#: src/views/settings/components/UpdateWorkspaceDescriptionForm.tsx:90
#: src/views/settings/components/UpdateDisplayNameForm.tsx:79
#: src/views/board/components/UpdateBoardSlugForm.tsx:173
#: src/views/board/components/UpdateBoardSlugForm.tsx:174
msgid "Update"
msgstr "Update"
#: src/components/LabelForm.tsx:233
#: src/components/LabelForm.tsx:236
msgid "Update label"
msgstr "Update label"
@@ -1599,6 +1604,10 @@ msgstr "View docs"
msgid "View our roadmap."
msgstr "View our roadmap."
#: src/views/public/board/index.tsx:143
msgid "View workspace"
msgstr "View workspace"
#: src/views/board/components/VisibilityButton.tsx:91
msgid "Visibility"
msgstr "Visibility"
@@ -1635,7 +1644,7 @@ msgstr "When Trello launched in 2011, it blew everyone away with its carefully d
msgid "Why make an open source Trello?"
msgstr "Why make an open source Trello?"
#: src/views/board/index.tsx:241
#: src/views/board/index.tsx:332
msgid "Workspace"
msgstr "Workspace"

File diff suppressed because one or more lines are too long

View File

@@ -28,7 +28,7 @@ msgid "{0} | {1}"
msgstr "{0} | {1}"
#. placeholder {0}: labelPublicIds.length
#: src/views/board/components/NewCardForm.tsx:365
#: src/views/board/components/NewCardForm.tsx:392
msgid "{0} labels"
msgstr "{0} etiquetas"
@@ -66,7 +66,7 @@ msgstr "Añadir una tarjeta"
msgid "Add a comment..."
msgstr "Añadir un comentario..."
#: src/components/Editor.tsx:302
#: src/components/Editor.tsx:303
msgid "Add description... (type '/' to open commands)"
msgstr "Añadir descripción... (escribe '/' para abrir comandos)"
@@ -178,7 +178,7 @@ msgstr "Portal de facturación"
msgid "Blog Post"
msgstr "Entrada de blog"
#: src/views/board/index.tsx:241
#: src/views/board/index.tsx:332
msgid "Board"
msgstr "Tablero"
@@ -190,7 +190,8 @@ msgstr "El nombre del tablero no puede exceder los 100 caracteres"
msgid "Board name is required"
msgstr "El nombre del tablero es obligatorio"
#: src/views/board/index.tsx:267
#: src/views/public/board/index.tsx:137
#: src/views/board/index.tsx:358
msgid "Board not found"
msgstr "Tablero no encontrado"
@@ -202,7 +203,7 @@ msgstr "La URL del tablero solo puede contener letras, números y guiones"
msgid "Board URL cannot exceed 60 characters"
msgstr "La URL del tablero no puede exceder los 60 caracteres"
#: src/views/public/board/index.tsx:53
#: src/views/public/board/index.tsx:59
msgid "Board URL copied to clipboard"
msgstr "URL del tablero copiada al portapapeles"
@@ -254,7 +255,7 @@ msgstr "Cancelar"
msgid "Card not found"
msgstr "Tarjeta no encontrada"
#: src/views/board/components/NewCardForm.tsx:254
#: src/views/board/components/NewCardForm.tsx:278
msgid "Card title"
msgstr "Título de la tarjeta"
@@ -347,7 +348,7 @@ msgid "Control who can view and edit your boards."
msgstr "Controla quién puede ver y editar tus tableros."
#: src/views/board/components/NewListForm.tsx:140
#: src/views/board/components/NewCardForm.tsx:391
#: src/views/board/components/NewCardForm.tsx:418
#: src/components/LabelForm.tsx:212
msgid "Create another"
msgstr "Crear otro"
@@ -356,15 +357,15 @@ msgstr "Crear otro"
msgid "Create board"
msgstr "Crear tablero"
#: src/views/board/components/NewCardForm.tsx:401
#: src/views/board/components/NewCardForm.tsx:428
msgid "Create card"
msgstr "Crear tarjeta"
#: src/components/LabelForm.tsx:233
#: src/components/LabelForm.tsx:236
msgid "Create label"
msgstr "Crear etiqueta"
#: src/views/board/components/NewListForm.tsx:148
#: src/views/board/components/NewListForm.tsx:152
msgid "Create list"
msgstr "Crear lista"
@@ -377,11 +378,11 @@ msgid "Create new key"
msgstr "Crear nueva clave"
#: src/views/card/components/LabelSelector.tsx:98
#: src/views/board/components/NewCardForm.tsx:327
#: src/views/board/components/NewCardForm.tsx:354
msgid "Create new label"
msgstr "Crear nueva etiqueta"
#: src/views/board/index.tsx:335
#: src/views/board/index.tsx:426
msgid "Create new list"
msgstr "Crear nueva lista"
@@ -676,7 +677,7 @@ msgstr "Comenzar"
msgid "Get started by creating a new board"
msgstr "Comienza creando un nuevo tablero"
#: src/views/board/index.tsx:327
#: src/views/board/index.tsx:418
msgid "Get started by creating a new list"
msgstr "Comienza creando una nueva lista"
@@ -826,7 +827,7 @@ msgid "Keep in mind that this action is irreversible."
msgstr "Ten en cuenta que esta acción es irreversible."
#: src/views/card/index.tsx:113
#: src/views/board/components/NewCardForm.tsx:331
#: src/views/board/components/NewCardForm.tsx:358
#: src/views/board/components/Filters.tsx:101
msgid "Labels"
msgstr "Etiquetas"
@@ -855,7 +856,7 @@ msgstr "Licencia"
msgid "Light"
msgstr "Claro"
#: src/views/public/board/index.tsx:51
#: src/views/public/board/index.tsx:57
msgid "Link copied"
msgstr "Enlace copiado"
@@ -893,7 +894,7 @@ msgstr "Prioridad media"
#: src/views/members/index.tsx:161
#: src/views/card/index.tsx:121
#: src/views/board/components/NewCardForm.tsx:290
#: src/views/board/components/NewCardForm.tsx:317
#: src/views/board/components/Filters.tsx:93
#: src/components/SideNavigation.tsx:73
msgid "Members"
@@ -933,7 +934,7 @@ msgstr "Nuevo"
msgid "New board"
msgstr "Nuevo tablero"
#: src/views/board/components/NewCardForm.tsx:237
#: src/views/board/components/NewCardForm.tsx:261
msgid "New card"
msgstr "Nueva tarjeta"
@@ -945,7 +946,7 @@ msgstr "Nueva importación"
msgid "New label"
msgstr "Nueva etiqueta"
#: src/views/board/index.tsx:304
#: src/views/board/index.tsx:395
#: src/views/board/components/NewListForm.tsx:112
msgid "New list"
msgstr "Nueva lista"
@@ -978,7 +979,7 @@ msgstr "No se encontraron tableros"
msgid "No credit card required"
msgstr "No se requiere tarjeta de crédito"
#: src/views/board/index.tsx:324
#: src/views/board/index.tsx:415
msgid "No lists"
msgstr "Sin listas"
@@ -1074,7 +1075,7 @@ msgstr "Por favor selecciona un archivo para subir."
#: src/views/board/components/VisibilityButton.tsx:53
#: src/views/board/components/UpdateBoardSlugForm.tsx:72
#: src/views/board/components/NewListForm.tsx:78
#: src/views/board/components/NewCardForm.tsx:128
#: src/views/board/components/NewCardForm.tsx:145
#: src/components/NewWorkspaceForm.tsx:41
#: src/components/FeedbackModal.tsx:41
msgid "Please try again later, or contact customer support."
@@ -1338,6 +1339,10 @@ msgstr "No podrán acceder a este espacio de trabajo."
msgid "This action can't be undone."
msgstr "Esta acción no se puede deshacer."
#: src/views/public/board/index.tsx:140
msgid "This board is private or does not exist"
msgstr "Este tablero es privado o no existe"
#: src/views/board/components/UpdateBoardSlugForm.tsx:135
msgid "This board URL has already been taken"
msgstr "Esta URL de tablero ya ha sido utilizada"
@@ -1383,7 +1388,7 @@ msgstr "Clasificación"
msgid "Unable to add comment"
msgstr "No se puede añadir el comentario"
#: src/views/board/components/NewCardForm.tsx:125
#: src/views/board/components/NewCardForm.tsx:142
msgid "Unable to create card"
msgstr "No se puede crear la tarjeta"
@@ -1477,11 +1482,11 @@ msgstr "Listas ilimitadas"
#: src/views/settings/components/UpdateWorkspaceNameForm.tsx:82
#: src/views/settings/components/UpdateWorkspaceDescriptionForm.tsx:90
#: src/views/settings/components/UpdateDisplayNameForm.tsx:79
#: src/views/board/components/UpdateBoardSlugForm.tsx:173
#: src/views/board/components/UpdateBoardSlugForm.tsx:174
msgid "Update"
msgstr "Actualizar"
#: src/components/LabelForm.tsx:233
#: src/components/LabelForm.tsx:236
msgid "Update label"
msgstr "Actualizar etiqueta"
@@ -1546,6 +1551,10 @@ msgstr "Ver documentación"
msgid "View our roadmap."
msgstr "Ver nuestra hoja de ruta."
#: src/views/public/board/index.tsx:143
msgid "View workspace"
msgstr "Ver espacio de trabajo"
#: src/views/board/components/VisibilityButton.tsx:91
msgid "Visibility"
msgstr "Visibilidad"
@@ -1578,7 +1587,7 @@ msgstr "Cuando Trello se lanzó en 2011, impresionó a todos con su simplicidad
msgid "Why make an open source Trello?"
msgstr "¿Por qué crear un Trello de código abierto?"
#: src/views/board/index.tsx:241
#: src/views/board/index.tsx:332
msgid "Workspace"
msgstr "Espacio de trabajo"

File diff suppressed because one or more lines are too long

View File

@@ -28,7 +28,7 @@ msgid "{0} | {1}"
msgstr "{0} | {1}"
#. placeholder {0}: labelPublicIds.length
#: src/views/board/components/NewCardForm.tsx:365
#: src/views/board/components/NewCardForm.tsx:392
msgid "{0} labels"
msgstr "{0} étiquettes"
@@ -66,7 +66,7 @@ msgstr "Ajouter une carte"
msgid "Add a comment..."
msgstr "Ajouter un commentaire..."
#: src/components/Editor.tsx:302
#: src/components/Editor.tsx:303
msgid "Add description... (type '/' to open commands)"
msgstr "Ajouter une description... (tapez '/' pour ouvrir les commandes)"
@@ -178,7 +178,7 @@ msgstr "Portail de facturation"
msgid "Blog Post"
msgstr "Article de blog"
#: src/views/board/index.tsx:241
#: src/views/board/index.tsx:332
msgid "Board"
msgstr "Tableau"
@@ -190,7 +190,8 @@ msgstr "Le nom du tableau ne peut pas dépasser 100 caractères"
msgid "Board name is required"
msgstr "Le nom du tableau est requis"
#: src/views/board/index.tsx:267
#: src/views/public/board/index.tsx:137
#: src/views/board/index.tsx:358
msgid "Board not found"
msgstr "Tableau introuvable"
@@ -202,7 +203,7 @@ msgstr "L'URL du tableau ne peut contenir que des lettres, des chiffres et des t
msgid "Board URL cannot exceed 60 characters"
msgstr "L'URL du tableau ne peut pas dépasser 60 caractères"
#: src/views/public/board/index.tsx:53
#: src/views/public/board/index.tsx:59
msgid "Board URL copied to clipboard"
msgstr "URL du tableau copiée dans le presse-papiers"
@@ -254,7 +255,7 @@ msgstr "Annuler"
msgid "Card not found"
msgstr "Carte introuvable"
#: src/views/board/components/NewCardForm.tsx:254
#: src/views/board/components/NewCardForm.tsx:278
msgid "Card title"
msgstr "Titre de la carte"
@@ -347,7 +348,7 @@ msgid "Control who can view and edit your boards."
msgstr "Contrôlez qui peut voir et modifier vos tableaux."
#: src/views/board/components/NewListForm.tsx:140
#: src/views/board/components/NewCardForm.tsx:391
#: src/views/board/components/NewCardForm.tsx:418
#: src/components/LabelForm.tsx:212
msgid "Create another"
msgstr "Créer un autre"
@@ -356,15 +357,15 @@ msgstr "Créer un autre"
msgid "Create board"
msgstr "Créer un tableau"
#: src/views/board/components/NewCardForm.tsx:401
#: src/views/board/components/NewCardForm.tsx:428
msgid "Create card"
msgstr "Créer une carte"
#: src/components/LabelForm.tsx:233
#: src/components/LabelForm.tsx:236
msgid "Create label"
msgstr "Créer une étiquette"
#: src/views/board/components/NewListForm.tsx:148
#: src/views/board/components/NewListForm.tsx:152
msgid "Create list"
msgstr "Créer une liste"
@@ -377,11 +378,11 @@ msgid "Create new key"
msgstr "Créer une nouvelle clé"
#: src/views/card/components/LabelSelector.tsx:98
#: src/views/board/components/NewCardForm.tsx:327
#: src/views/board/components/NewCardForm.tsx:354
msgid "Create new label"
msgstr "Créer une nouvelle étiquette"
#: src/views/board/index.tsx:335
#: src/views/board/index.tsx:426
msgid "Create new list"
msgstr "Créer une nouvelle liste"
@@ -676,7 +677,7 @@ msgstr "Commencer"
msgid "Get started by creating a new board"
msgstr "Commencez par créer un nouveau tableau"
#: src/views/board/index.tsx:327
#: src/views/board/index.tsx:418
msgid "Get started by creating a new list"
msgstr "Commencez par créer une nouvelle liste"
@@ -826,7 +827,7 @@ msgid "Keep in mind that this action is irreversible."
msgstr "Gardez à l'esprit que cette action est irréversible."
#: src/views/card/index.tsx:113
#: src/views/board/components/NewCardForm.tsx:331
#: src/views/board/components/NewCardForm.tsx:358
#: src/views/board/components/Filters.tsx:101
msgid "Labels"
msgstr "Étiquettes"
@@ -855,7 +856,7 @@ msgstr "Licence"
msgid "Light"
msgstr "Clair"
#: src/views/public/board/index.tsx:51
#: src/views/public/board/index.tsx:57
msgid "Link copied"
msgstr "Lien copié"
@@ -893,7 +894,7 @@ msgstr "Priorité moyenne"
#: src/views/members/index.tsx:161
#: src/views/card/index.tsx:121
#: src/views/board/components/NewCardForm.tsx:290
#: src/views/board/components/NewCardForm.tsx:317
#: src/views/board/components/Filters.tsx:93
#: src/components/SideNavigation.tsx:73
msgid "Members"
@@ -933,7 +934,7 @@ msgstr "Nouveau"
msgid "New board"
msgstr "Nouveau tableau"
#: src/views/board/components/NewCardForm.tsx:237
#: src/views/board/components/NewCardForm.tsx:261
msgid "New card"
msgstr "Nouvelle carte"
@@ -945,7 +946,7 @@ msgstr "Nouvel import"
msgid "New label"
msgstr "Nouvelle étiquette"
#: src/views/board/index.tsx:304
#: src/views/board/index.tsx:395
#: src/views/board/components/NewListForm.tsx:112
msgid "New list"
msgstr "Nouvelle liste"
@@ -978,7 +979,7 @@ msgstr "Aucun tableau trouvé"
msgid "No credit card required"
msgstr "Aucune carte de crédit requise"
#: src/views/board/index.tsx:324
#: src/views/board/index.tsx:415
msgid "No lists"
msgstr "Aucune liste"
@@ -1074,7 +1075,7 @@ msgstr "Veuillez sélectionner un fichier à télécharger."
#: src/views/board/components/VisibilityButton.tsx:53
#: src/views/board/components/UpdateBoardSlugForm.tsx:72
#: src/views/board/components/NewListForm.tsx:78
#: src/views/board/components/NewCardForm.tsx:128
#: src/views/board/components/NewCardForm.tsx:145
#: src/components/NewWorkspaceForm.tsx:41
#: src/components/FeedbackModal.tsx:41
msgid "Please try again later, or contact customer support."
@@ -1338,6 +1339,10 @@ msgstr "Ils ne pourront plus accéder à cet espace de travail."
msgid "This action can't be undone."
msgstr "Cette action ne peut pas être annulée."
#: src/views/public/board/index.tsx:140
msgid "This board is private or does not exist"
msgstr "Ce tableau est privé ou n'existe pas"
#: src/views/board/components/UpdateBoardSlugForm.tsx:135
msgid "This board URL has already been taken"
msgstr "Cette URL de tableau est déjà utilisée"
@@ -1383,7 +1388,7 @@ msgstr "Triage"
msgid "Unable to add comment"
msgstr "Impossible d'ajouter un commentaire"
#: src/views/board/components/NewCardForm.tsx:125
#: src/views/board/components/NewCardForm.tsx:142
msgid "Unable to create card"
msgstr "Impossible de créer la carte"
@@ -1477,11 +1482,11 @@ msgstr "Listes illimitées"
#: src/views/settings/components/UpdateWorkspaceNameForm.tsx:82
#: src/views/settings/components/UpdateWorkspaceDescriptionForm.tsx:90
#: src/views/settings/components/UpdateDisplayNameForm.tsx:79
#: src/views/board/components/UpdateBoardSlugForm.tsx:173
#: src/views/board/components/UpdateBoardSlugForm.tsx:174
msgid "Update"
msgstr "Mettre à jour"
#: src/components/LabelForm.tsx:233
#: src/components/LabelForm.tsx:236
msgid "Update label"
msgstr "Mettre à jour l'étiquette"
@@ -1546,6 +1551,10 @@ msgstr "Voir la documentation"
msgid "View our roadmap."
msgstr "Consultez notre feuille de route."
#: src/views/public/board/index.tsx:143
msgid "View workspace"
msgstr "Voir l'espace de travail"
#: src/views/board/components/VisibilityButton.tsx:91
msgid "Visibility"
msgstr "Visibilité"
@@ -1578,7 +1587,7 @@ msgstr "Quand Trello a été lancé en 2011, il a impressionné tout le monde pa
msgid "Why make an open source Trello?"
msgstr "Pourquoi créer un Trello open source ?"
#: src/views/board/index.tsx:241
#: src/views/board/index.tsx:332
msgid "Workspace"
msgstr "Espace de travail"

File diff suppressed because one or more lines are too long

View File

@@ -28,7 +28,7 @@ msgid "{0} | {1}"
msgstr "{0} | {1}"
#. placeholder {0}: labelPublicIds.length
#: src/views/board/components/NewCardForm.tsx:365
#: src/views/board/components/NewCardForm.tsx:392
msgid "{0} labels"
msgstr "{0} etichette"
@@ -66,7 +66,7 @@ msgstr "Aggiungi una carta"
msgid "Add a comment..."
msgstr "Aggiungi un commento..."
#: src/components/Editor.tsx:302
#: src/components/Editor.tsx:303
msgid "Add description... (type '/' to open commands)"
msgstr "Aggiungi descrizione... (digita '/' per aprire i comandi)"
@@ -178,7 +178,7 @@ msgstr "Portale di fatturazione"
msgid "Blog Post"
msgstr "Post del blog"
#: src/views/board/index.tsx:241
#: src/views/board/index.tsx:332
msgid "Board"
msgstr "Bacheca"
@@ -190,7 +190,8 @@ msgstr "Il nome della bacheca non può superare i 100 caratteri"
msgid "Board name is required"
msgstr "Il nome della bacheca è obbligatorio"
#: src/views/board/index.tsx:267
#: src/views/public/board/index.tsx:137
#: src/views/board/index.tsx:358
msgid "Board not found"
msgstr "Bacheca non trovata"
@@ -202,7 +203,7 @@ msgstr "L'URL della bacheca può contenere solo lettere, numeri e trattini"
msgid "Board URL cannot exceed 60 characters"
msgstr "L'URL della bacheca non può superare i 60 caratteri"
#: src/views/public/board/index.tsx:53
#: src/views/public/board/index.tsx:59
msgid "Board URL copied to clipboard"
msgstr "URL della bacheca copiato negli appunti"
@@ -254,7 +255,7 @@ msgstr "Annulla"
msgid "Card not found"
msgstr "Carta non trovata"
#: src/views/board/components/NewCardForm.tsx:254
#: src/views/board/components/NewCardForm.tsx:278
msgid "Card title"
msgstr "Titolo della carta"
@@ -347,7 +348,7 @@ msgid "Control who can view and edit your boards."
msgstr "Controlla chi può visualizzare e modificare le tue bacheche."
#: src/views/board/components/NewListForm.tsx:140
#: src/views/board/components/NewCardForm.tsx:391
#: src/views/board/components/NewCardForm.tsx:418
#: src/components/LabelForm.tsx:212
msgid "Create another"
msgstr "Crea un altro"
@@ -356,15 +357,15 @@ msgstr "Crea un altro"
msgid "Create board"
msgstr "Crea bacheca"
#: src/views/board/components/NewCardForm.tsx:401
#: src/views/board/components/NewCardForm.tsx:428
msgid "Create card"
msgstr "Crea carta"
#: src/components/LabelForm.tsx:233
#: src/components/LabelForm.tsx:236
msgid "Create label"
msgstr "Crea etichetta"
#: src/views/board/components/NewListForm.tsx:148
#: src/views/board/components/NewListForm.tsx:152
msgid "Create list"
msgstr "Crea lista"
@@ -377,11 +378,11 @@ msgid "Create new key"
msgstr "Crea nuova chiave"
#: src/views/card/components/LabelSelector.tsx:98
#: src/views/board/components/NewCardForm.tsx:327
#: src/views/board/components/NewCardForm.tsx:354
msgid "Create new label"
msgstr "Crea nuova etichetta"
#: src/views/board/index.tsx:335
#: src/views/board/index.tsx:426
msgid "Create new list"
msgstr "Crea nuova lista"
@@ -676,7 +677,7 @@ msgstr "Inizia ora"
msgid "Get started by creating a new board"
msgstr "Inizia creando una nuova bacheca"
#: src/views/board/index.tsx:327
#: src/views/board/index.tsx:418
msgid "Get started by creating a new list"
msgstr "Inizia creando una nuova lista"
@@ -826,7 +827,7 @@ msgid "Keep in mind that this action is irreversible."
msgstr "Tieni presente che questa azione è irreversibile."
#: src/views/card/index.tsx:113
#: src/views/board/components/NewCardForm.tsx:331
#: src/views/board/components/NewCardForm.tsx:358
#: src/views/board/components/Filters.tsx:101
msgid "Labels"
msgstr "Etichette"
@@ -855,7 +856,7 @@ msgstr "Licenza"
msgid "Light"
msgstr "Chiaro"
#: src/views/public/board/index.tsx:51
#: src/views/public/board/index.tsx:57
msgid "Link copied"
msgstr "Link copiato"
@@ -893,7 +894,7 @@ msgstr "Media priorità"
#: src/views/members/index.tsx:161
#: src/views/card/index.tsx:121
#: src/views/board/components/NewCardForm.tsx:290
#: src/views/board/components/NewCardForm.tsx:317
#: src/views/board/components/Filters.tsx:93
#: src/components/SideNavigation.tsx:73
msgid "Members"
@@ -933,7 +934,7 @@ msgstr "Nuovo"
msgid "New board"
msgstr "Nuova bacheca"
#: src/views/board/components/NewCardForm.tsx:237
#: src/views/board/components/NewCardForm.tsx:261
msgid "New card"
msgstr "Nuova carta"
@@ -945,7 +946,7 @@ msgstr "Nuova importazione"
msgid "New label"
msgstr "Nuova etichetta"
#: src/views/board/index.tsx:304
#: src/views/board/index.tsx:395
#: src/views/board/components/NewListForm.tsx:112
msgid "New list"
msgstr "Nuova lista"
@@ -978,7 +979,7 @@ msgstr "Nessuna bacheca trovata"
msgid "No credit card required"
msgstr "Nessuna carta di credito richiesta"
#: src/views/board/index.tsx:324
#: src/views/board/index.tsx:415
msgid "No lists"
msgstr "Nessuna lista"
@@ -1074,7 +1075,7 @@ msgstr "Seleziona un file da caricare."
#: src/views/board/components/VisibilityButton.tsx:53
#: src/views/board/components/UpdateBoardSlugForm.tsx:72
#: src/views/board/components/NewListForm.tsx:78
#: src/views/board/components/NewCardForm.tsx:128
#: src/views/board/components/NewCardForm.tsx:145
#: src/components/NewWorkspaceForm.tsx:41
#: src/components/FeedbackModal.tsx:41
msgid "Please try again later, or contact customer support."
@@ -1338,6 +1339,10 @@ msgstr "Non potranno accedere a questo spazio di lavoro."
msgid "This action can't be undone."
msgstr "Questa azione non può essere annullata."
#: src/views/public/board/index.tsx:140
msgid "This board is private or does not exist"
msgstr "Questa bacheca è privata o non esiste"
#: src/views/board/components/UpdateBoardSlugForm.tsx:135
msgid "This board URL has already been taken"
msgstr "Questo URL della bacheca è già stato utilizzato"
@@ -1383,7 +1388,7 @@ msgstr "Smistamento"
msgid "Unable to add comment"
msgstr "Impossibile aggiungere il commento"
#: src/views/board/components/NewCardForm.tsx:125
#: src/views/board/components/NewCardForm.tsx:142
msgid "Unable to create card"
msgstr "Impossibile creare la scheda"
@@ -1477,11 +1482,11 @@ msgstr "Liste illimitate"
#: src/views/settings/components/UpdateWorkspaceNameForm.tsx:82
#: src/views/settings/components/UpdateWorkspaceDescriptionForm.tsx:90
#: src/views/settings/components/UpdateDisplayNameForm.tsx:79
#: src/views/board/components/UpdateBoardSlugForm.tsx:173
#: src/views/board/components/UpdateBoardSlugForm.tsx:174
msgid "Update"
msgstr "Aggiorna"
#: src/components/LabelForm.tsx:233
#: src/components/LabelForm.tsx:236
msgid "Update label"
msgstr "Aggiorna etichetta"
@@ -1546,6 +1551,10 @@ msgstr "Visualizza documenti"
msgid "View our roadmap."
msgstr "Visualizza la nostra roadmap."
#: src/views/public/board/index.tsx:143
msgid "View workspace"
msgstr "Visualizza spazio di lavoro"
#: src/views/board/components/VisibilityButton.tsx:91
msgid "Visibility"
msgstr "Visibilità"
@@ -1578,7 +1587,7 @@ msgstr "Quando Trello fu lanciato nel 2011, stupì tutti con la sua semplicità
msgid "Why make an open source Trello?"
msgstr "Perché creare un Trello open source?"
#: src/views/board/index.tsx:241
#: src/views/board/index.tsx:332
msgid "Workspace"
msgstr "Spazio di lavoro"

File diff suppressed because one or more lines are too long

View File

@@ -28,7 +28,7 @@ msgid "{0} | {1}"
msgstr "{0} | {1}"
#. placeholder {0}: labelPublicIds.length
#: src/views/board/components/NewCardForm.tsx:365
#: src/views/board/components/NewCardForm.tsx:392
msgid "{0} labels"
msgstr "{0} labels"
@@ -66,7 +66,7 @@ msgstr "Voeg een kaart toe"
msgid "Add a comment..."
msgstr "Voeg een reactie toe..."
#: src/components/Editor.tsx:302
#: src/components/Editor.tsx:303
msgid "Add description... (type '/' to open commands)"
msgstr "Voeg beschrijving toe... (typ '/' om commando's te openen)"
@@ -178,7 +178,7 @@ msgstr "Factureringsportaal"
msgid "Blog Post"
msgstr "Blogbericht"
#: src/views/board/index.tsx:241
#: src/views/board/index.tsx:332
msgid "Board"
msgstr "Bord"
@@ -190,7 +190,8 @@ msgstr "Bordnaam mag niet langer zijn dan 100 tekens"
msgid "Board name is required"
msgstr "Bordnaam is verplicht"
#: src/views/board/index.tsx:267
#: src/views/public/board/index.tsx:137
#: src/views/board/index.tsx:358
msgid "Board not found"
msgstr "Bord niet gevonden"
@@ -202,7 +203,7 @@ msgstr "Board URL kan alleen letters, cijfers en koppeltekens bevatten"
msgid "Board URL cannot exceed 60 characters"
msgstr "Board URL mag niet langer zijn dan 60 tekens"
#: src/views/public/board/index.tsx:53
#: src/views/public/board/index.tsx:59
msgid "Board URL copied to clipboard"
msgstr "Board URL gekopieerd naar klembord"
@@ -254,7 +255,7 @@ msgstr "Annuleren"
msgid "Card not found"
msgstr "Kaart niet gevonden"
#: src/views/board/components/NewCardForm.tsx:254
#: src/views/board/components/NewCardForm.tsx:278
msgid "Card title"
msgstr "Kaarttitel"
@@ -347,7 +348,7 @@ msgid "Control who can view and edit your boards."
msgstr "Bepaal wie je borden kan bekijken en bewerken."
#: src/views/board/components/NewListForm.tsx:140
#: src/views/board/components/NewCardForm.tsx:391
#: src/views/board/components/NewCardForm.tsx:418
#: src/components/LabelForm.tsx:212
msgid "Create another"
msgstr "Maak nog een"
@@ -356,15 +357,15 @@ msgstr "Maak nog een"
msgid "Create board"
msgstr "Maak bord"
#: src/views/board/components/NewCardForm.tsx:401
#: src/views/board/components/NewCardForm.tsx:428
msgid "Create card"
msgstr "Maak kaart"
#: src/components/LabelForm.tsx:233
#: src/components/LabelForm.tsx:236
msgid "Create label"
msgstr "Maak label"
#: src/views/board/components/NewListForm.tsx:148
#: src/views/board/components/NewListForm.tsx:152
msgid "Create list"
msgstr "Lijst maken"
@@ -377,11 +378,11 @@ msgid "Create new key"
msgstr "Nieuwe sleutel aanmaken"
#: src/views/card/components/LabelSelector.tsx:98
#: src/views/board/components/NewCardForm.tsx:327
#: src/views/board/components/NewCardForm.tsx:354
msgid "Create new label"
msgstr "Maak nieuw label"
#: src/views/board/index.tsx:335
#: src/views/board/index.tsx:426
msgid "Create new list"
msgstr "Maak nieuwe lijst"
@@ -676,7 +677,7 @@ msgstr "Aan de slag"
msgid "Get started by creating a new board"
msgstr "Begin door een nieuw bord te maken"
#: src/views/board/index.tsx:327
#: src/views/board/index.tsx:418
msgid "Get started by creating a new list"
msgstr "Begin door een nieuwe lijst te maken"
@@ -826,7 +827,7 @@ msgid "Keep in mind that this action is irreversible."
msgstr "Houd er rekening mee dat deze actie onomkeerbaar is."
#: src/views/card/index.tsx:113
#: src/views/board/components/NewCardForm.tsx:331
#: src/views/board/components/NewCardForm.tsx:358
#: src/views/board/components/Filters.tsx:101
msgid "Labels"
msgstr "Labels"
@@ -855,7 +856,7 @@ msgstr "Licentie"
msgid "Light"
msgstr "Licht"
#: src/views/public/board/index.tsx:51
#: src/views/public/board/index.tsx:57
msgid "Link copied"
msgstr "Link gekopieerd"
@@ -893,7 +894,7 @@ msgstr "Gemiddelde prioriteit"
#: src/views/members/index.tsx:161
#: src/views/card/index.tsx:121
#: src/views/board/components/NewCardForm.tsx:290
#: src/views/board/components/NewCardForm.tsx:317
#: src/views/board/components/Filters.tsx:93
#: src/components/SideNavigation.tsx:73
msgid "Members"
@@ -933,7 +934,7 @@ msgstr "Nieuw"
msgid "New board"
msgstr "Nieuw bord"
#: src/views/board/components/NewCardForm.tsx:237
#: src/views/board/components/NewCardForm.tsx:261
msgid "New card"
msgstr "Nieuwe kaart"
@@ -945,7 +946,7 @@ msgstr "Nieuwe import"
msgid "New label"
msgstr "Nieuw label"
#: src/views/board/index.tsx:304
#: src/views/board/index.tsx:395
#: src/views/board/components/NewListForm.tsx:112
msgid "New list"
msgstr "Nieuwe lijst"
@@ -978,7 +979,7 @@ msgstr "Geen borden gevonden"
msgid "No credit card required"
msgstr "Geen creditcard vereist"
#: src/views/board/index.tsx:324
#: src/views/board/index.tsx:415
msgid "No lists"
msgstr "Geen lijsten"
@@ -1074,7 +1075,7 @@ msgstr "Selecteer een bestand om te uploaden."
#: src/views/board/components/VisibilityButton.tsx:53
#: src/views/board/components/UpdateBoardSlugForm.tsx:72
#: src/views/board/components/NewListForm.tsx:78
#: src/views/board/components/NewCardForm.tsx:128
#: src/views/board/components/NewCardForm.tsx:145
#: src/components/NewWorkspaceForm.tsx:41
#: src/components/FeedbackModal.tsx:41
msgid "Please try again later, or contact customer support."
@@ -1338,6 +1339,10 @@ msgstr "Ze zullen geen toegang meer hebben tot deze werkruimte."
msgid "This action can't be undone."
msgstr "Deze actie kan niet ongedaan worden gemaakt."
#: src/views/public/board/index.tsx:140
msgid "This board is private or does not exist"
msgstr "Dit bord is privé of bestaat niet"
#: src/views/board/components/UpdateBoardSlugForm.tsx:135
msgid "This board URL has already been taken"
msgstr "Deze board URL is al in gebruik"
@@ -1383,7 +1388,7 @@ msgstr "Triage"
msgid "Unable to add comment"
msgstr "Kan geen reactie toevoegen"
#: src/views/board/components/NewCardForm.tsx:125
#: src/views/board/components/NewCardForm.tsx:142
msgid "Unable to create card"
msgstr "Kan kaart niet aanmaken"
@@ -1477,11 +1482,11 @@ msgstr "Onbeperkt aantal lijsten"
#: src/views/settings/components/UpdateWorkspaceNameForm.tsx:82
#: src/views/settings/components/UpdateWorkspaceDescriptionForm.tsx:90
#: src/views/settings/components/UpdateDisplayNameForm.tsx:79
#: src/views/board/components/UpdateBoardSlugForm.tsx:173
#: src/views/board/components/UpdateBoardSlugForm.tsx:174
msgid "Update"
msgstr "Bijwerken"
#: src/components/LabelForm.tsx:233
#: src/components/LabelForm.tsx:236
msgid "Update label"
msgstr "Label bijwerken"
@@ -1546,6 +1551,10 @@ msgstr "Bekijk documentatie"
msgid "View our roadmap."
msgstr "Bekijk onze roadmap."
#: src/views/public/board/index.tsx:143
msgid "View workspace"
msgstr "Werkruimte bekijken"
#: src/views/board/components/VisibilityButton.tsx:91
msgid "Visibility"
msgstr "Zichtbaarheid"
@@ -1578,7 +1587,7 @@ msgstr "Toen Trello in 2011 werd gelanceerd, blies het iedereen omver met zijn z
msgid "Why make an open source Trello?"
msgstr "Waarom een open source Trello maken?"
#: src/views/board/index.tsx:241
#: src/views/board/index.tsx:332
msgid "Workspace"
msgstr "Werkruimte"

View File

@@ -1,5 +1,15 @@
import { createContext, useContext, useState } from "react";
interface ModalState {
contentType: string;
entityId?: string;
entityLabel?: string;
}
interface Props {
children: React.ReactNode;
}
type ModalContextType = {
isOpen: boolean;
openModal: (
@@ -11,33 +21,64 @@ type ModalContextType = {
modalContentType: string;
entityId: string;
entityLabel: string;
modalStates: Record<string, any>;
setModalState: (modalType: string, state: any) => void;
getModalState: (modalType: string) => any;
clearModalState: (modalType: string) => void;
clearAllModalStates: () => void;
};
interface Props {
children: React.ReactNode;
}
const ModalContext = createContext<ModalContextType | undefined>(undefined);
export const ModalProvider: React.FC<Props> = ({ children }) => {
const [isOpen, setIsOpen] = useState(false);
const [entityId, setEntityId] = useState("");
const [entityLabel, setEntityLabel] = useState("");
const [modalContentType, setModalContentType] = useState("");
const [modalStack, setModalStack] = useState<ModalState[]>([]);
const [modalStates, setModalStates] = useState<Record<string, any>>({});
const isOpen = modalStack.length > 0;
const currentModal = modalStack[modalStack.length - 1];
const modalContentType = currentModal?.contentType || "";
const entityId = currentModal?.entityId || "";
const entityLabel = currentModal?.entityLabel || "";
const openModal = (
contentType: string,
entityId?: string,
entityLabel?: string,
) => {
setIsOpen(true);
setModalContentType(contentType);
if (entityId) setEntityId(entityId);
if (entityLabel) setEntityLabel(entityLabel);
const newModal: ModalState = { contentType, entityId, entityLabel };
setModalStack(prev => [...prev, newModal]);
};
const closeModal = () => {
setIsOpen(false);
setModalStack(prev => {
if (prev.length <= 1) {
return [];
}
return prev.slice(0, -1);
});
};
const setModalState = (modalType: string, state: any) => {
setModalStates(prev => ({
...prev,
[modalType]: state
}));
};
const getModalState = (modalType: string) => {
return modalStates[modalType];
};
const clearModalState = (modalType: string) => {
setModalStates(prev => {
const newStates = { ...prev };
delete newStates[modalType];
return newStates;
});
};
const clearAllModalStates = () => {
setModalStates({});
};
return (
@@ -49,6 +90,11 @@ export const ModalProvider: React.FC<Props> = ({ children }) => {
modalContentType,
entityId,
entityLabel,
modalStates,
setModalState,
getModalState,
clearModalState,
clearAllModalStates,
}}
>
{children}

View File

@@ -18,6 +18,7 @@ import Editor from "~/components/Editor";
import Input from "~/components/Input";
import LabelIcon from "~/components/LabelIcon";
import Toggle from "~/components/Toggle";
import { useModalFormState } from "~/hooks/useModalFormState";
import { useModal } from "~/providers/modal";
import { usePopup } from "~/providers/popup";
import { api } from "~/utils/api";
@@ -49,17 +50,24 @@ export function NewCardForm({
const utils = api.useUtils();
// persists the form values
const { formState, saveFormState } = useModalFormState<NewCardFormInput>({
modalType: "NEW_CARD",
initialValues: {
title: "",
description: "",
listPublicId,
labelPublicIds: [],
memberPublicIds: [],
isCreateAnotherEnabled: false,
position: "start",
},
resetOnClose: true,
});
const { register, handleSubmit, reset, setValue, watch } =
useForm<NewCardFormInput>({
defaultValues: {
title: "",
description: "",
listPublicId,
labelPublicIds: [],
memberPublicIds: [],
isCreateAnotherEnabled: false,
position: "start",
},
values: formState,
});
const labelPublicIds = watch("labelPublicIds") || [];
@@ -67,6 +75,15 @@ export function NewCardForm({
const isCreateAnotherEnabled = watch("isCreateAnotherEnabled");
const position = watch("position");
const title = watch("title");
const description = watch("description");
// saving form state whenever form values change
useEffect(() => {
const subscription = watch((data) => {
saveFormState(data as NewCardFormInput);
});
return () => subscription.unsubscribe();
}, [watch, saveFormState]);
const { data: boardData } = api.board.byId.useQuery(queryParams, {
enabled: !!boardPublicId,
@@ -131,17 +148,24 @@ export function NewCardForm({
},
onSuccess: async () => {
const isCreateAnotherEnabled = watch("isCreateAnotherEnabled");
if (!isCreateAnotherEnabled) closeModal();
if (!isCreateAnotherEnabled) {
// close modal (state will auto-clear due to resetOnClose: true)
closeModal();
} else {
// reset form for creating another card
const newFormState = {
title: "",
description: "",
listPublicId: watch("listPublicId"),
labelPublicIds: [],
memberPublicIds: [],
isCreateAnotherEnabled,
position,
};
reset(newFormState);
saveFormState(newFormState);
}
await utils.board.byId.invalidate(queryParams);
reset({
title: "",
description: "",
listPublicId: watch("listPublicId"),
labelPublicIds: [],
memberPublicIds: [],
isCreateAnotherEnabled,
position,
});
},
});
@@ -264,8 +288,11 @@ export function NewCardForm({
<div className="mt-2">
<div className="block max-h-48 min-h-24 w-full overflow-y-auto rounded-md border-0 bg-dark-300 bg-white/5 px-3 py-2 text-sm shadow-sm ring-1 ring-inset ring-light-600 focus-within:ring-2 focus-within:ring-inset focus-within:ring-light-700 dark:ring-dark-700 dark:focus-within:ring-dark-700 sm:leading-6">
<Editor
content=""
onChange={(value) => setValue("description", value)}
content={description}
onChange={(value) => {
setValue("description", value);
saveFormState({ ...formState, description: value });
}}
/>
</div>
</div>

View File

@@ -145,7 +145,12 @@ export function NewListForm({
/>
<div>
<Button type="submit">{t`Create list`}</Button>
<Button
type="submit"
disabled={createList.isPending || !watch("name")}
>
{t`Create list`}
</Button>
</div>
</div>
</form>

View File

@@ -167,7 +167,8 @@ export function UpdateBoardSlugForm({
!isDirty ||
updateBoardSlug.isPending ||
errors.slug?.message !== undefined ||
isBoardSlugAvailable?.isReserved
isBoardSlugAvailable?.isReserved ||
checkBoardSlugAvailability.isLoading
}
>
{t`Update`}

View File

@@ -45,7 +45,7 @@ export default function BoardPage() {
const utils = api.useUtils();
const { showPopup } = usePopup();
const { workspace } = useWorkspace();
const { openModal, modalContentType, entityId } = useModal();
const { openModal, modalContentType, entityId, isOpen } = useModal();
const [selectedPublicListId, setSelectedPublicListId] =
useState<PublicListId>("");
const [isInitialLoading, setIsInitialLoading] = useState(true);
@@ -235,6 +235,97 @@ export default function BoardPage() {
}
};
const renderModalContent = () => {
return (
<>
<Modal
modalSize="sm"
isVisible={isOpen && modalContentType === "DELETE_BOARD"}
>
<DeleteBoardConfirmation boardPublicId={boardId ?? ""} />
</Modal>
<Modal
modalSize="sm"
isVisible={isOpen && modalContentType === "DELETE_LIST"}
>
<DeleteListConfirmation
listPublicId={selectedPublicListId}
queryParams={queryParams}
/>
</Modal>
<Modal
modalSize="md"
isVisible={isOpen && modalContentType === "NEW_CARD"}
>
<NewCardForm
boardPublicId={boardId ?? ""}
listPublicId={selectedPublicListId}
queryParams={queryParams}
/>
</Modal>
<Modal
modalSize="sm"
isVisible={isOpen && modalContentType === "NEW_LIST"}
>
<NewListForm
boardPublicId={boardId ?? ""}
queryParams={queryParams}
/>
</Modal>
<Modal
modalSize="sm"
isVisible={isOpen && modalContentType === "NEW_WORKSPACE"}
>
<NewWorkspaceForm />
</Modal>
<Modal
modalSize="sm"
isVisible={isOpen && modalContentType === "NEW_LABEL"}
>
<LabelForm boardPublicId={boardId ?? ""} refetch={refetchBoard} />
</Modal>
<Modal
modalSize="sm"
isVisible={isOpen && modalContentType === "EDIT_LABEL"}
>
<LabelForm
boardPublicId={boardId ?? ""}
refetch={refetchBoard}
isEdit
/>
</Modal>
<Modal
modalSize="sm"
isVisible={isOpen && modalContentType === "DELETE_LABEL"}
>
<DeleteLabelConfirmation
refetch={refetchBoard}
labelPublicId={entityId}
/>
</Modal>
<Modal
modalSize="sm"
isVisible={isOpen && modalContentType === "UPDATE_BOARD_SLUG"}
>
<UpdateBoardSlugForm
boardPublicId={boardId ?? ""}
workspaceSlug={workspace.slug ?? ""}
boardSlug={boardData?.slug ?? ""}
queryParams={queryParams}
/>
</Modal>
</>
);
};
return (
<>
<PageHead
@@ -422,63 +513,7 @@ export default function BoardPage() {
</>
) : null}
</div>
<Modal
modalSize={
modalContentType === "NEW_CARD" ||
modalContentType === "NEW_FEEDBACK"
? "md"
: "sm"
}
>
{modalContentType === "NEW_FEEDBACK" && <FeedbackModal />}
{modalContentType === "DELETE_BOARD" && (
<DeleteBoardConfirmation boardPublicId={boardId ?? ""} />
)}
{modalContentType === "DELETE_LIST" && (
<DeleteListConfirmation
listPublicId={selectedPublicListId}
queryParams={queryParams}
/>
)}
{modalContentType === "NEW_CARD" && (
<NewCardForm
boardPublicId={boardId ?? ""}
listPublicId={selectedPublicListId}
queryParams={queryParams}
/>
)}
{modalContentType === "NEW_LIST" && (
<NewListForm
boardPublicId={boardId ?? ""}
queryParams={queryParams}
/>
)}
{modalContentType === "NEW_WORKSPACE" && <NewWorkspaceForm />}
{modalContentType === "NEW_LABEL" && (
<LabelForm boardPublicId={boardId ?? ""} refetch={refetchBoard} />
)}
{modalContentType === "EDIT_LABEL" && (
<LabelForm
boardPublicId={boardId ?? ""}
refetch={refetchBoard}
isEdit
/>
)}
{modalContentType === "DELETE_LABEL" && (
<DeleteLabelConfirmation
refetch={refetchBoard}
labelPublicId={entityId}
/>
)}
{modalContentType === "UPDATE_BOARD_SLUG" && (
<UpdateBoardSlugForm
boardPublicId={boardId ?? ""}
workspaceSlug={workspace.slug ?? ""}
boardSlug={boardData?.slug ?? ""}
queryParams={queryParams}
/>
)}
</Modal>
{renderModalContent()}
</div>
</>
);

View File

@@ -97,7 +97,7 @@ export default function MembersPage() {
</td>
<td
className={twMerge(
"w-auto min-w-[120px] sm:w-[35%] sm:min-w-[150px]",
"w-auto min-w-[120px] overflow-visible sm:w-[35%] sm:min-w-[150px]",
isLastRow && "rounded-br-lg",
)}
>
@@ -121,7 +121,7 @@ export default function MembersPage() {
</div>
<div
className={twMerge(
"relative",
"relative z-50",
(workspace.role !== "admin" || showSkeleton) && "hidden",
)}
>
@@ -172,10 +172,10 @@ export default function MembersPage() {
</div>
<div className="mt-8 flow-root">
<div className="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
<div className="inline-block min-w-full px-4 py-2 align-middle sm:px-6 lg:px-8">
<div className="-mx-4 -my-2 sm:-mx-6 lg:-mx-8">
<div className="inline-block min-w-full overflow-x-auto px-4 py-2 pb-16 align-middle sm:px-6 lg:px-8">
<div className="h-full shadow ring-1 ring-black ring-opacity-5 sm:rounded-lg">
<table className="min-w-full divide-y divide-light-600 dark:divide-dark-600">
<table className="min-w-full divide-y divide-light-600 overflow-visible dark:divide-dark-600">
<thead className="rounded-t-lg bg-light-300 dark:bg-dark-200">
<tr>
<th
@@ -192,7 +192,7 @@ export default function MembersPage() {
</th>
</tr>
</thead>
<tbody className="divide-y divide-light-600 bg-light-50 dark:divide-dark-600 dark:bg-dark-100">
<tbody className="divide-y divide-light-600 overflow-visible bg-light-50 dark:divide-dark-600 dark:bg-dark-100">
{!isLoading &&
data?.members.map((member, index) => (
<TableRow

View File

@@ -3,8 +3,9 @@ import { useRouter } from "next/router";
import { t } from "@lingui/core/macro";
import { keepPreviousData } from "@tanstack/react-query";
import { useEffect, useState } from "react";
import { HiLink } from "react-icons/hi2";
import { HiLink, HiOutlineLockClosed } from "react-icons/hi2";
import Button from "~/components/Button";
import Modal from "~/components/modal";
import { PageHead } from "~/components/PageHead";
import PatternedBackground from "~/components/PatternedBackground";
@@ -28,9 +29,14 @@ export default function PublicBoardView() {
? router.query.boardSlug[0]
: router.query.boardSlug;
const workspaceSlug = Array.isArray(router.query.workspaceSlug)
? router.query.workspaceSlug[0]
: router.query.workspaceSlug;
const { data, isLoading } = api.board.bySlug.useQuery(
{
boardSlug: boardSlug ?? "",
workspaceSlug: workspaceSlug ?? "",
members: formatToArray(router.query.members),
labels: formatToArray(router.query.labels),
},
@@ -116,17 +122,30 @@ export default function PublicBoardView() {
)}
</div>
<div className="scrollbar-w-none scrollbar-track-rounded-[4px] scrollbar-thumb-rounded-[4px] scrollbar-h-[8px] relative flex-1 overflow-y-hidden overflow-x-scroll overscroll-contain scrollbar scrollbar-track-light-200 scrollbar-thumb-light-400 dark:scrollbar-track-dark-100 dark:scrollbar-thumb-dark-300">
<div className="scrollbar-w-none scrollbar-track-rounded-[4px] scrollbar-thumb-rounded-[4px] scrollbar-h-[8px] relative h-full flex-1 overflow-y-hidden overflow-x-scroll overscroll-contain scrollbar scrollbar-track-light-200 scrollbar-thumb-light-400 dark:scrollbar-track-dark-100 dark:scrollbar-thumb-dark-300">
{isLoading ? (
<div className="ml-[2rem] flex">
<div className="0 mr-5 h-[500px] w-[18rem] animate-pulse rounded-md bg-light-200 dark:bg-dark-100" />
<div className="0 mr-5 h-[275px] w-[18rem] animate-pulse rounded-md bg-light-200 dark:bg-dark-100" />
<div className="0 mr-5 h-[375px] w-[18rem] animate-pulse rounded-md bg-light-200 dark:bg-dark-100" />
</div>
) : !data ? (
<div className="z-10 flex h-full w-full flex-col items-center justify-center space-y-8 pb-[150px]">
<div className="flex flex-col items-center">
<HiOutlineLockClosed className="h-10 w-10 text-light-800 dark:text-dark-800" />
<p className="mb-2 mt-4 text-[14px] font-bold text-light-1000 dark:text-dark-950">
{t`Board not found`}
</p>
<p className="text-[14px] text-light-900 dark:text-dark-900">
{t`This board is private or does not exist`}
</p>
</div>
<Button href={`/${workspaceSlug}`}>{t`View workspace`}</Button>
</div>
) : (
<div className="flex">
<div className="min-w-[2rem]" />
{data?.lists.map((list) => (
{data.lists.map((list) => (
<div
key={list.publicId}
className="dark-text-dark-1000 mr-5 h-fit min-w-[18rem] max-w-[18rem] rounded-md border border-light-400 bg-light-300 py-2 pl-2 pr-1 text-neutral-900 dark:border-dark-300 dark:bg-dark-100"

View File

@@ -23,13 +23,15 @@ services:
- STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY}
- STRIPE_WEBHOOK_SECRET=${STRIPE_WEBHOOK_SECRET}
# SMTP
# Email
- SMTP_HOST=${SMTP_HOST}
- SMTP_PORT=${SMTP_PORT}
- SMTP_USER=${SMTP_USER}
- SMTP_PASSWORD=${SMTP_PASSWORD}
- SMTP_SECURE=${SMTP_SECURE}
- EMAIL_FROM=${EMAIL_FROM}
- PLUNK_API_KEY=${PLUNK_API_KEY}
- PLUNK_API_URL=${PLUNK_API_URL}
# S3 storage
- S3_ACCESS_KEY_ID=${S3_ACCESS_KEY_ID}

View File

@@ -111,15 +111,21 @@ export const boardRouter = createTRPCRouter({
.meta({
openapi: {
method: "GET",
path: "/board/{boardSlug}",
path: "/workspaces/{workspaceSlug}/boards/{boardSlug}",
summary: "Get board by slug",
description: "Retrieves a board by its slug",
description:
"Retrieves a board by its slug within a specific workspace",
tags: ["Boards"],
protect: false,
},
})
.input(
z.object({
workspaceSlug: z
.string()
.min(3)
.max(24)
.regex(/^(?![-]+$)[a-zA-Z0-9-]+$/),
boardSlug: z
.string()
.min(3)
@@ -131,10 +137,26 @@ export const boardRouter = createTRPCRouter({
)
.output(z.custom<Awaited<ReturnType<typeof boardRepo.getBySlug>>>())
.query(async ({ ctx, input }) => {
const result = await boardRepo.getBySlug(ctx.db, input.boardSlug, {
members: input.members ?? [],
labels: input.labels ?? [],
});
const workspace = await workspaceRepo.getBySlugWithBoards(
ctx.db,
input.workspaceSlug,
);
if (!workspace)
throw new TRPCError({
message: `Workspace with slug ${input.workspaceSlug} not found`,
code: "NOT_FOUND",
});
const result = await boardRepo.getBySlug(
ctx.db,
input.boardSlug,
workspace.id,
{
members: input.members ?? [],
labels: input.labels ?? [],
},
);
return result;
}),
@@ -276,6 +298,21 @@ export const boardRouter = createTRPCRouter({
await assertUserInWorkspace(ctx.db, userId, board.workspaceId);
if (input.slug) {
const isBoardSlugAvailable = await boardRepo.isBoardSlugAvailable(
ctx.db,
input.slug,
board.workspaceId,
);
if (!isBoardSlugAvailable) {
throw new TRPCError({
message: `Board slug ${input.slug} is not available`,
code: "BAD_REQUEST",
});
}
}
const result = await boardRepo.update(ctx.db, {
name: input.name,
slug: input.slug,
@@ -407,11 +444,23 @@ export const boardRouter = createTRPCRouter({
}),
)
.query(async ({ ctx, input }) => {
const board = await boardRepo.getWorkspaceAndBoardIdByBoardPublicId(
ctx.db,
input.boardPublicId,
);
if (!board)
throw new TRPCError({
message: `Board with public ID ${input.boardPublicId} not found`,
code: "NOT_FOUND",
});
const isBoardSlugAvailable = await boardRepo.isBoardSlugAvailable(
ctx.db,
input.boardSlug,
input.boardPublicId,
board.workspaceId,
);
return {
isReserved: !isBoardSlugAvailable,
};

View File

@@ -11,7 +11,7 @@ import type { dbClient } from "@kan/db/client";
import * as memberRepo from "@kan/db/repository/member.repo";
import * as userRepo from "@kan/db/repository/user.repo";
import * as schema from "@kan/db/schema";
import { sendEmail } from "@kan/email";
import { cloudMailerClient, sendEmail } from "@kan/email";
import { createStripeClient } from "@kan/stripe";
export const configuredProviders = socialProviderList.reduce<
@@ -183,6 +183,17 @@ export const initAuth = (db: dbClient) => {
return Promise.resolve(true);
},
async after(user) {
if (cloudMailerClient) {
await cloudMailerClient.events.track({
event: "user-signup",
email: user.email,
data: {
name: user.name,
userId: user.id,
},
});
}
if (
user.image &&
!user.image.includes(process.env.NEXT_PUBLIC_STORAGE_DOMAIN!)

View File

@@ -1,4 +1,4 @@
import { and, asc, desc, eq, exists, inArray, isNull, or, sql } from "drizzle-orm";
import { and, asc, desc, eq, inArray, isNull, or } from "drizzle-orm";
import type { dbClient } from "@kan/db/client";
import type { BoardVisibilityStatus } from "@kan/db/schema";
@@ -201,6 +201,7 @@ export const getByPublicId = async (
export const getBySlug = async (
db: dbClient,
boardSlug: string,
workspaceId: number,
filters: {
members: string[];
labels: string[];
@@ -293,6 +294,7 @@ export const getBySlug = async (
},
where: and(
eq(boards.slug, boardSlug),
eq(boards.workspaceId, workspaceId),
isNull(boards.deletedAt),
eq(boards.visibility, "public"),
),
@@ -481,30 +483,18 @@ export const getWorkspaceAndBoardIdByBoardPublicId = async (
export const isBoardSlugAvailable = async (
db: dbClient,
boardSlug: string,
boardPublicId: string,
workspaceId: number,
) => {
const result = await db
.select({ id: boards.id })
.from(boards)
.where(
and(
eq(boards.publicId, boardPublicId),
exists(
db
.select({ id: boards.id })
.from(boards)
.where(
and(
eq(boards.slug, boardSlug),
eq(boards.workspaceId, sql`${boards.workspaceId}`), // Reference outer query's workspaceId
isNull(boards.deletedAt),
),
)
.limit(1),
),
),
)
.limit(1);
const result = await db.query.boards.findFirst({
columns: {
id: true,
},
where: and(
eq(boards.slug, boardSlug),
eq(boards.workspaceId, workspaceId),
isNull(boards.deletedAt),
),
});
return result.length === 0;
return result === undefined;
};

View File

@@ -23,6 +23,7 @@
"typecheck": "tsc --noEmit --emitDeclarationOnly false"
},
"dependencies": {
"@plunk/node": "^3.0.3",
"@react-email/components": "^0.0.25",
"nodemailer": "^7.0.3",
"react-email": "^3.0.1"

View File

@@ -0,0 +1,8 @@
import Plunk from "@plunk/node";
export const cloudMailerClient =
process.env.NEXT_PUBLIC_KAN_ENV === "cloud"
? new Plunk(process.env.PLUNK_API_KEY ?? "", {
baseUrl: process.env.PLUNK_API_URL,
})
: null;

View File

@@ -1,3 +1,4 @@
export const name = "email";
export { sendEmail } from "./sendEmail";
export { cloudMailerClient } from "./cloudMailerClient";

View File

@@ -1,6 +1,7 @@
import { render } from "@react-email/render";
import nodemailer from "nodemailer";
import { cloudMailerClient } from "./cloudMailerClient";
import JoinWorkspaceTemplate from "./templates/join-workspace";
import MagicLinkTemplate from "./templates/magic-link";
import ResetPasswordTemplate from "./templates/reset-password";
@@ -44,6 +45,15 @@ export const sendEmail = async (
html,
};
if (cloudMailerClient) {
const response = await cloudMailerClient.emails.send({
...options,
body: html,
});
return response;
}
const response = await transporter.sendMail(options);
if (!response.accepted.length) {

10
pnpm-lock.yaml generated
View File

@@ -409,6 +409,9 @@ importers:
packages/email:
dependencies:
'@plunk/node':
specifier: ^3.0.3
version: 3.0.3
'@react-email/components':
specifier: ^0.0.25
version: 0.0.25(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -2194,6 +2197,9 @@ packages:
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
'@plunk/node@3.0.3':
resolution: {integrity: sha512-f3NT418/EdLjebQ4arAwR64cnz7d5H7i1CeKh8+B0cVBrwzHvF+Q+QkhutF+FAp49yuI/BYc71qQH+RFc2fxxQ==}
'@popperjs/core@2.11.8':
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
@@ -8910,6 +8916,10 @@ snapshots:
'@pkgjs/parseargs@0.11.0':
optional: true
'@plunk/node@3.0.3':
dependencies:
tslib: 2.8.1
'@popperjs/core@2.11.8': {}
'@react-aria/focus@3.19.0(react@18.3.1)':

View File

@@ -109,7 +109,9 @@
"S3_FORCE_PATH_STYLE",
"PORT",
"BETTER_AUTH_SECRET",
"BETTER_AUTH_TRUSTED_ORIGINS"
"BETTER_AUTH_TRUSTED_ORIGINS",
"PLUNK_API_KEY",
"PLUNK_API_URL"
],
"globalPassThroughEnv": [
"NODE_ENV",