Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
aa34fe7
fix being able to accept multiple times by invite code
doomlab Apr 17, 2026
d7216e9
fix task refresh on window tab switching
doomlab Apr 17, 2026
67a8fbd
fix issue with new task flashing
doomlab Apr 17, 2026
256b41e
Merge pull request #511 from STAPLE-verse/501-task-creation-page-refr…
doomlab Apr 17, 2026
e071b0e
Merge pull request #512 from STAPLE-verse/invite-use-check
doomlab Apr 17, 2026
f770966
adding in global search across pages
doomlab Apr 18, 2026
8559f63
update table search for filtering columns
doomlab Apr 19, 2026
6a7c8c3
fix table bouncing reseting
doomlab May 27, 2026
296ec8f
make description a markdown!
doomlab May 27, 2026
1f82c22
add markdown to description
doomlab May 27, 2026
6ae3126
make descriptions markdown
doomlab May 27, 2026
7e367fb
update to use markdown!
doomlab May 27, 2026
614a0d5
issue #506 fix checkboxes
doomlab May 27, 2026
7636f63
Merge pull request #515 from STAPLE-verse/502-table-search-on-all-not…
doomlab May 27, 2026
9e0dc95
Merge pull request #516 from STAPLE-verse/503-update-form-descriptions
doomlab May 27, 2026
5c0fbb8
update visual themes
doomlab May 27, 2026
e63d3fe
add multiselect check box
doomlab May 27, 2026
d895dbe
add form folders and tags
doomlab May 27, 2026
a96e86b
Merge pull request #517 from STAPLE-verse/503-update-form-descriptions
doomlab May 27, 2026
ec391e2
Merge branch 'form-updates-for-marker' into dev
doomlab May 27, 2026
9b45ed8
Merge pull request #518 from STAPLE-verse/dev
doomlab May 27, 2026
c23b3f3
merge in dev
doomlab May 27, 2026
05ee4fb
add button to top
doomlab May 27, 2026
bd9461d
add folder dropdown and filter options
doomlab May 27, 2026
bbc69ec
add new edit tab to overall form page
doomlab May 27, 2026
bca3cf7
add save button
doomlab May 27, 2026
8fa34f1
form folder styling
doomlab May 27, 2026
fa3a87f
add tags to tables for forms
doomlab May 27, 2026
faadd92
add server side searching
doomlab May 27, 2026
8eb94f9
add downloads and sorts to notes
doomlab May 27, 2026
26c8394
add feedback form
doomlab May 27, 2026
b8a5a8e
add feedback form
doomlab May 27, 2026
97ac391
fix typescript issue
doomlab May 27, 2026
cde3c26
Merge pull request #519 from STAPLE-verse/form-updates-for-marker
doomlab May 27, 2026
497d030
Merge branch 'dev' into 514-add-the-ability-to-sort-notes-by-date-cre…
doomlab May 27, 2026
37b9816
Merge pull request #520 from STAPLE-verse/514-add-the-ability-to-sort…
doomlab May 27, 2026
568331c
Merge pull request #521 from STAPLE-verse/508-feedback-form
doomlab May 27, 2026
0c14626
allow people to be added to teams before joining for auto team tasks
doomlab May 27, 2026
9953f58
merge teams
doomlab Jun 1, 2026
0a6fb85
fix typescript
doomlab Jun 1, 2026
1333850
Merge pull request #522 from STAPLE-verse/504-allow-team-creation-bef…
doomlab Jun 1, 2026
9705ded
Merge pull request #523 from STAPLE-verse/dev
doomlab Jun 1, 2026
c01add7
clean up form styling
doomlab Jun 1, 2026
2fe5423
fix vertical visuals
doomlab Jun 1, 2026
e2fe862
fix visuals for tables
doomlab Jun 1, 2026
b706f6d
update delete and archive form information
doomlab Jun 1, 2026
84dd993
updating forms for marker deployment
doomlab Jun 1, 2026
67246c6
fix archive issue
doomlab Jun 1, 2026
c597dd2
update form view
doomlab Jun 1, 2026
f00b6d9
clean up archive and other information
doomlab Jun 1, 2026
bba3693
clean up form structure
doomlab Jun 1, 2026
bdc205b
fixed up forms
doomlab Jun 1, 2026
2741e74
fix search and show buttons
doomlab Jun 1, 2026
5f21c50
note visuals
doomlab Jun 2, 2026
05fd672
make icons consistent
doomlab Jun 2, 2026
a35a042
consistent icons
doomlab Jun 2, 2026
4590044
break up the dashboard
doomlab Jun 2, 2026
bd09954
fix typescript errors
doomlab Jun 2, 2026
1c4c898
Merge pull request #525 from STAPLE-verse/fix-visual-errors
doomlab Jun 2, 2026
31f6828
allow for data to be anonymous
doomlab Jun 2, 2026
422a58d
Merge pull request #526 from STAPLE-verse/524-make-form-data-anonymous
doomlab Jun 2, 2026
3e696d8
copy projects!
doomlab Jun 2, 2026
d1d4e94
only on where you are pm
doomlab Jun 2, 2026
1fa1567
Merge pull request #527 from STAPLE-verse/505-project-templates
doomlab Jun 2, 2026
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
20 changes: 20 additions & 0 deletions db/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ model User {
// relational information
projects ProjectMember[]
forms Form[]
folders Folder[]
roles Role[]
ProjectPrivilege ProjectPrivilege[]
}
Expand Down Expand Up @@ -92,6 +93,7 @@ model ProjectMember {
taskLogAssignedTo TaskLog[] @relation("assignedTo")
taskLogCompletedBy TaskLog[] @relation("completedBy")
reassignmentInvitations Invitation[]
pendingInvitations Invitation[] @relation("PendingTeamInvitations")
comments Comment[]
tags Json?
commentReadStatus CommentReadStatus[]
Expand Down Expand Up @@ -123,6 +125,7 @@ model Invitation {
tags Json?
reassignmentFor Int? // Foreign key to ProjectMember
projectMember ProjectMember? @relation(fields: [reassignmentFor], references: [id]) // Relation to ProjectMember
pendingTeams ProjectMember[] @relation("PendingTeamInvitations")
}

model Role {
Expand Down Expand Up @@ -212,6 +215,7 @@ model Task {
element Element? @relation(fields: [elementId], references: [id])
autoAssignNew AutoAssignNew @default(NONE)
anonymous Boolean @default(false)
anonymousResponses Boolean @default(false)
}

model TaskLog {
Expand Down Expand Up @@ -279,6 +283,18 @@ model Milestone {
endDate DateTime?
}

model Folder {
id Int @id @default(autoincrement())
name String
userId Int
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
forms Form[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

@@unique([name, userId])
}

model Form {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
Expand All @@ -287,6 +303,9 @@ model Form {
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
versions FormVersion[]
archived Boolean @default(false)
tags Json?
folderId Int?
folder Folder? @relation(fields: [folderId], references: [id])
}

model FormVersion {
Expand All @@ -296,6 +315,7 @@ model FormVersion {
version Int
schema Json
uiSchema Json?
archived Boolean @default(false)
createdAt DateTime @default(now())
form Form @relation(fields: [formId], references: [id], onDelete: Cascade)
tasks Task[]
Expand Down
32 changes: 32 additions & 0 deletions integrations/emails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,38 @@ staple.helpdesk@gmail.com
}
}

export function createFeedbackMsg({
fromUsername,
fromEmail,
subject,
message,
}: {
fromUsername: string
fromEmail: string
subject: string
message: string
}) {
return {
from: "STAPLE <app@staplescience.com>",
to: "staple.helpdesk@gmail.com",
replyTo: fromEmail ? `${fromUsername} <${fromEmail}>` : "STAPLE App",
subject: `[Feedback] ${subject}`,
html: `
<html>
<body>
<center><img src="https://raw.githubusercontent.com/STAPLE-verse/STAPLE-verse.github.io/main/pics/staple_email.jpg"
alt="STAPLE Logo" height="200"></center>
<h3>Feedback from ${fromUsername}</h3>
<p><strong>From:</strong> ${fromUsername} &lt;${fromEmail}&gt;</p>
<p><strong>Subject:</strong> ${subject}</p>
<p><strong>Message:</strong></p>
<p>${message.replace(/\n/g, "<br>")}</p>
</body>
</html>
`,
}
}

export function createEditProfileMsg(user) {
return {
from: "STAPLE <app@staplescience.com>",
Expand Down
11 changes: 10 additions & 1 deletion src/blitz-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,14 @@ export const authConfig = {
}

export const { withBlitz } = setupBlitzClient({
plugins: [AuthClientPlugin(authConfig), BlitzRpcPlugin({})],
plugins: [
AuthClientPlugin(authConfig),
BlitzRpcPlugin({
reactQueryOptions: {
queries: {
refetchOnWindowFocus: false,
},
},
}),
],
})
134 changes: 131 additions & 3 deletions src/core/components/DaisyTheme.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,18 @@ import {
getUiOptions,
getSubmitButtonOptions,
SubmitButtonProps,
schemaRequiresTrueValue,
descriptionId,
ariaDescribedByIds,
enumOptionsIsSelected,
enumOptionsSelectValue,
enumOptionsDeselectValue,
enumOptionsValueForIndex,
optionId,
} from "@rjsf/utils"
import ReactMarkdown from "react-markdown"
import remarkGfm from "remark-gfm"
import remarkBreaks from "remark-breaks"

import { ThemeProps } from "@rjsf/core"

Expand Down Expand Up @@ -74,9 +85,12 @@ function MyDescriptionField<
}
if (typeof description === "string") {
return (
<p id={id} className="text-md italic">
{description}
</p>
<div
id={id}
className="markdown-display prose max-w-none dark:prose-invert text-md italic mb-2"
>
<ReactMarkdown remarkPlugins={[remarkGfm, remarkBreaks]}>{description}</ReactMarkdown>
</div>
)
} else {
return (
Expand Down Expand Up @@ -186,6 +200,118 @@ const MyEmailWidget = (props: WidgetProps) => {
)
}

const MyCheckboxWidget = (props: WidgetProps) => {
const {
id,
value,
disabled,
readonly,
label,
hideLabel,
onChange,
onBlur,
onFocus,
options,
schema,
uiSchema,
registry,
} = props
const DescriptionFieldTemplate = getTemplate("DescriptionFieldTemplate", registry, options)
const description = options.description ?? schema.description
const required = schemaRequiresTrueValue(schema)

return (
<div className="field-checkbox">
{!hideLabel && label && (
<label className="text-lg font-bold block mb-1" htmlFor={id}>
{label}
{required && <span className="italic">{REQUIRED_FIELD_SYMBOL}</span>}
</label>
)}
{!hideLabel && !!description && (
<DescriptionFieldTemplate
id={descriptionId(id)}
description={description}
schema={schema}
uiSchema={uiSchema}
registry={registry}
/>
)}
<label className="flex items-center gap-2 mt-1 cursor-pointer">
<input
type="checkbox"
id={id}
name={id}
checked={typeof value === "undefined" ? false : value}
required={required}
disabled={disabled || readonly}
aria-describedby={ariaDescribedByIds(id)}
onChange={(e) => onChange(e.target.checked)}
onBlur={(e) => onBlur(id, e.target.checked)}
onFocus={(e) => onFocus(id, e.target.checked)}
/>
</label>
</div>
)
}

const MyCheckboxesWidget = (props: WidgetProps) => {
const {
id,
disabled,
options,
value,
readonly,
onChange,
onBlur,
onFocus,
autofocus = false,
} = props
const { enumOptions, enumDisabled, emptyValue } = options
const checkboxesValues = Array.isArray(value) ? value : [value]

return (
<div className="checkboxes-group" id={id}>
{Array.isArray(enumOptions) &&
enumOptions.map((option, index) => {
const checked = enumOptionsIsSelected(option.value, checkboxesValues)
const itemDisabled =
Array.isArray(enumDisabled) && enumDisabled.indexOf(option.value) !== -1
const disabledCls = disabled || itemDisabled || readonly ? "disabled" : ""

return (
<label key={index} className={`checkboxes-option ${disabledCls}`}>
<input
type="checkbox"
id={optionId(id, index)}
name={id}
checked={checked}
value={String(index)}
disabled={disabled || itemDisabled || readonly}
autoFocus={autofocus && index === 0}
onChange={(event) => {
if (event.target.checked) {
onChange(enumOptionsSelectValue(index, checkboxesValues, enumOptions))
} else {
onChange(enumOptionsDeselectValue(index, checkboxesValues, enumOptions))
}
}}
onBlur={({ target: { value: v } }) =>
onBlur(id, enumOptionsValueForIndex(v, enumOptions, emptyValue))
}
onFocus={({ target: { value: v } }) =>
onFocus(id, enumOptionsValueForIndex(v, enumOptions, emptyValue))
}
aria-describedby={ariaDescribedByIds(id)}
/>
<span>{option.label}</span>
</label>
)
})}
</div>
)
}

// create Registry information
// templates
const myTemplates: Partial<TemplatesType> = {
Expand Down Expand Up @@ -217,6 +343,8 @@ const myTemplates: Partial<TemplatesType> = {
const myWidgets: RegistryWidgetsType = {
TextWidget: MyTextWidget,
EmailWidget: MyEmailWidget,
CheckboxWidget: MyCheckboxWidget,
CheckboxesWidget: MyCheckboxesWidget,
}

// create the overall theme to use on the other page
Expand Down
78 changes: 78 additions & 0 deletions src/core/components/FeedbackButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { useRef } from "react"
import { useMutation } from "@blitzjs/rpc"
import { QuestionMarkCircleIcon } from "@heroicons/react/24/outline"
import { z } from "zod"
import sendFeedback from "src/core/mutations/sendFeedback"
import toast from "react-hot-toast"
import { Form } from "src/core/components/fields/Form"
import { LabeledTextField } from "src/core/components/fields/LabeledTextField"
import LabeledTextAreaField from "src/core/components/fields/LabeledTextAreaField"

const FeedbackSchema = z.object({
subject: z.string().min(1, "Subject is required").max(200),
message: z.string().min(1, "Message is required").max(5000),
})

export const FeedbackButton = () => {
const [sendFeedbackMutation] = useMutation(sendFeedback)
const dialogRef = useRef<HTMLDialogElement>(null)

const openModal = () => dialogRef.current?.showModal()
const closeModal = () => dialogRef.current?.close()

const handleSubmit = async (values: { subject: string; message: string }) => {
try {
await sendFeedbackMutation(values)
toast.success("Feedback sent! We'll be in touch.")
closeModal()
} catch {
toast.error("Failed to send feedback. Please try again.")
}
}

return (
<>
<button
onClick={openModal}
className="fixed bottom-6 right-6 z-50 btn btn-circle btn-primary shadow-lg"
title="Send feedback"
>
<QuestionMarkCircleIcon className="w-6 h-6" />
</button>

<dialog
ref={dialogRef}
className="modal"
onClick={(e) => {
if (e.target === dialogRef.current) closeModal()
}}
>
<div className="modal-box" onClick={(e) => e.stopPropagation()}>
<h3 className="font-bold text-lg mb-4">Send Feedback</h3>
<Form
schema={FeedbackSchema}
onSubmit={handleSubmit}
submitText="Send"
cancelText="Cancel"
onCancel={closeModal}
summitOnRight
>
<LabeledTextField
name="subject"
label="Subject"
placeholder="Brief description of your feedback"
className="input mb-4 w-full text-primary input-primary input-bordered border-2 bg-base-300"
/>
<LabeledTextAreaField
name="message"
label="Message"
placeholder="Describe your issue or suggestion..."
className="textarea textarea-primary textarea-bordered border-2 bg-base-300 text-primary mb-4 w-full"
rows={6}
/>
</Form>
</div>
</dialog>
</>
)
}
Loading
Loading