diff --git a/.direnv/bin/nix-direnv-reload b/.direnv/bin/nix-direnv-reload
new file mode 100755
index 0000000..d07ca27
--- /dev/null
+++ b/.direnv/bin/nix-direnv-reload
@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+set -e
+if [[ ! -d "/home/amy/code/public/oss/frontend" ]]; then
+ echo "Cannot find source directory; Did you move it?"
+ echo "(Looking for "/home/amy/code/public/oss/frontend")"
+ echo 'Cannot force reload with this script - use "direnv reload" manually and then try again'
+ exit 1
+fi
+
+# rebuild the cache forcefully
+_nix_direnv_force_reload=1 direnv exec "/home/amy/code/public/oss/frontend" true
+
+# Update the mtime for .envrc.
+# This will cause direnv to reload again - but without re-building.
+touch "/home/amy/code/public/oss/frontend/.envrc"
+
+# Also update the timestamp of whatever profile_rc we have.
+# This makes sure that we know we are up to date.
+touch -r "/home/amy/code/public/oss/frontend/.envrc" "/home/amy/code/public/oss/frontend/.direnv"/*.rc
diff --git a/.envrc b/.envrc
new file mode 100644
index 0000000..3550a30
--- /dev/null
+++ b/.envrc
@@ -0,0 +1 @@
+use flake
diff --git a/README.md b/README.md
index e24c032..b235431 100644
--- a/README.md
+++ b/README.md
@@ -49,4 +49,4 @@ While maintaining Donetick's commitment to open source, this hosted option will
## License
-This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details. I might consider changing it later to something else
+This project is licensed under the AGPLv3 License. See the [LICENSE](LICENSE) file for more details. I might consider changing it later to something else
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000..d28337e
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,61 @@
+{
+ "nodes": {
+ "flake-parts": {
+ "inputs": {
+ "nixpkgs-lib": "nixpkgs-lib"
+ },
+ "locked": {
+ "lastModified": 1743550720,
+ "narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=",
+ "owner": "hercules-ci",
+ "repo": "flake-parts",
+ "rev": "c621e8422220273271f52058f618c94e405bb0f5",
+ "type": "github"
+ },
+ "original": {
+ "owner": "hercules-ci",
+ "repo": "flake-parts",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1743583204,
+ "narHash": "sha256-F7n4+KOIfWrwoQjXrL2wD9RhFYLs2/GGe/MQY1sSdlE=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "2c8d3f48d33929642c1c12cd243df4cc7d2ce434",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixos-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "nixpkgs-lib": {
+ "locked": {
+ "lastModified": 1743296961,
+ "narHash": "sha256-b1EdN3cULCqtorQ4QeWgLMrd5ZGOjLSLemfa00heasc=",
+ "owner": "nix-community",
+ "repo": "nixpkgs.lib",
+ "rev": "e4822aea2a6d1cdd36653c134cacfd64c97ff4fa",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-community",
+ "repo": "nixpkgs.lib",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "flake-parts": "flake-parts",
+ "nixpkgs": "nixpkgs"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000..1ecdaaa
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,23 @@
+{
+ description = "Description for the project";
+
+ inputs = {
+ flake-parts.url = "github:hercules-ci/flake-parts";
+ nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
+ };
+
+ outputs = inputs@{ flake-parts, ... }:
+ flake-parts.lib.mkFlake { inherit inputs; } {
+ systems = [ "x86_64-linux" "aarch64-linux" "aarch64-darwin" "x86_64-darwin" ];
+ perSystem = { config, self', inputs', pkgs, system, ... }: {
+ devShells.default = pkgs.mkShell {
+ packages = with pkgs; [
+ nodePackages.npm
+ nodejs
+ ];
+ };
+ };
+ flake = {
+ };
+ };
+}
diff --git a/src/utils/Chores.jsx b/src/utils/Chores.jsx
index 729a9ac..b85ad4b 100644
--- a/src/utils/Chores.jsx
+++ b/src/utils/Chores.jsx
@@ -179,9 +179,9 @@ export const notInCompletionWindow = chore => {
export const ChoreFilters = userProfile => ({
anyone: () => true,
assigned_to_me: chore => {
- return chore.assignedTo && chore.assignedTo === userProfile.id
+ return chore.assignedTo && chore.assignedTo === userProfile?.id
},
assigned_to_others: chore => {
- return chore.assignedTo && chore.assignedTo !== userProfile.id
+ return chore.assignedTo && chore.assignedTo !== userProfile?.id
},
})
diff --git a/src/views/Authorization/LoginView.jsx b/src/views/Authorization/LoginView.jsx
index 476f468..bd93055 100644
--- a/src/views/Authorization/LoginView.jsx
+++ b/src/views/Authorization/LoginView.jsx
@@ -115,7 +115,7 @@ const LoginView = () => {
Navigate('/forgot-password')
}
const generateRandomState = () => {
- const randomState = Math.random().toString(36).substring(7)
+ const randomState = Math.random().toString(32).substring(5)
localStorage.setItem('authState', randomState)
return randomState
diff --git a/src/views/Chores/MyChores.jsx b/src/views/Chores/MyChores.jsx
index 94c2fd7..ae4497e 100644
--- a/src/views/Chores/MyChores.jsx
+++ b/src/views/Chores/MyChores.jsx
@@ -74,7 +74,7 @@ const MyChores = () => {
JSON.parse(localStorage.getItem('openChoreSections')) || {},
)
const [selectedChoreFilter, setSelectedChoreFilter] = useState(
- JSON.parse(localStorage.getItem('selectedChoreFilter')) || 'anyone',
+ localStorage.getItem('selectedChoreFilter') || 'anyone',
)
const [searchTerm, setSearchTerm] = useState('')
const [performers, setPerformers] = useState([])
@@ -98,13 +98,12 @@ const MyChores = () => {
throw new Error(userProfileResponse.statusText)
}
Promise.all([
+ userProfileResponse.json(),
choresResponse.json(),
usersResponse.json(),
- userProfileResponse.json(),
]).then(data => {
- const [choresData, usersData, userProfileData] = data
+ const [userProfileData, choresData, usersData] = data
setUserProfile(userProfileData.res)
- choresData.res.sort(ChoreSorter)
setChores(choresData.res)
setFilteredChores(choresData.res)
setPerformers(usersData.res)
@@ -139,7 +138,11 @@ const MyChores = () => {
const sortedChores = choresData.res.sort(ChoreSorter)
setChores(sortedChores)
setFilteredChores(sortedChores)
- const sections = ChoresGrouper(selectedChoreSection, sortedChores)
+ const sections = ChoresGrouper(
+ selectedChoreSection,
+ sortedChores,
+ ChoreFilters(userProfile)[selectedChoreFilter],
+ )
setChoreSections(sections)
if (localStorage.getItem('openChoreSections') === null) {
setSelectedChoreSectionWithCache(selectedChoreSection)
@@ -178,7 +181,7 @@ const MyChores = () => {
}
const setSelectedChoreFilterWithCache = value => {
setSelectedChoreFilter(value)
- localStorage.setItem('selectedChoreFilter', JSON.stringify(value))
+ localStorage.setItem('selectedChoreFilter', value)
}
const updateChores = newChore => {
@@ -186,7 +189,13 @@ const MyChores = () => {
newChores.push(newChore)
setChores(newChores)
setFilteredChores(newChores)
- setChoreSections(ChoresGrouper(selectedChoreSection, newChores))
+ setChoreSections(
+ ChoresGrouper(
+ selectedChoreSection,
+ newChores,
+ ChoreFilters(userProfile)[selectedChoreFilter],
+ ),
+ )
setSearchFilter('All')
}
const handleMenuOutsideClick = event => {
@@ -262,7 +271,13 @@ const MyChores = () => {
}
setChores(newChores)
setFilteredChores(newFilteredChores)
- setChoreSections(ChoresGrouper(selectedChoreSection, newChores))
+ setChoreSections(
+ ChoresGrouper(
+ selectedChoreSection,
+ newChores,
+ ChoreFilters(userProfile)[selectedChoreFilter],
+ ),
+ )
switch (event) {
case 'completed':
@@ -293,7 +308,13 @@ const MyChores = () => {
)
setChores(newChores)
setFilteredChores(newFilteredChores)
- setChoreSections(ChoresGrouper(selectedChoreSection, newChores))
+ setChoreSections(
+ ChoresGrouper(
+ selectedChoreSection,
+ newChores,
+ ChoreFilters(userProfile)[selectedChoreFilter],
+ ),
+ )
}
const searchOptions = {
@@ -449,7 +470,11 @@ const MyChores = () => {
)
}}
onItemSelect={selected => {
- const section = ChoresGrouper(selected.value, chores)
+ const section = ChoresGrouper(
+ selected.value,
+ chores,
+ ChoreFilters(userProfile)[selectedChoreFilter],
+ )
setChoreSections(section)
setSelectedChoreSectionWithCache(selected.value)
setOpenChoreSectionsWithCache(
diff --git a/src/views/Settings/NotificationSetting.jsx b/src/views/Settings/NotificationSetting.jsx
index a5abec9..105260a 100644
--- a/src/views/Settings/NotificationSetting.jsx
+++ b/src/views/Settings/NotificationSetting.jsx
@@ -98,6 +98,10 @@ const NotificationSetting = () => {
const [chatID, setChatID] = useState(
userProfile?.notification_target?.target_id,
)
+ const [webhookURL, setWebhookURL] = useState(
+ userProfile?.notification_target?.target_id,
+ )
+
const [error, setError] = useState('')
const SaveValidation = () => {
switch (notificationTarget) {
@@ -116,6 +120,12 @@ const NotificationSetting = () => {
return false
}
break
+ case '4':
+ if (webhookURL === '') {
+ setError('Webhook URL is required')
+ return false
+ }
+ break
default:
break
}
@@ -126,7 +136,8 @@ const NotificationSetting = () => {
if (!SaveValidation()) return
UpdateNotificationTarget({
- target: chatID,
+ // 4 = Discord
+ target: notificationTarget === '4' ? webhookURL : chatID,
type: Number(notificationTarget),
}).then(resp => {
if (resp.status != 200) {
@@ -134,13 +145,26 @@ const NotificationSetting = () => {
return
}
- setUserProfile({
- ...userProfile,
- notification_target: {
- target: chatID,
- type: Number(notificationTarget),
- },
- })
+ // Discord
+ if (notificationTarget === '4') {
+ setUserProfile({
+ ...userProfile,
+ notification_target: {
+ target: webhookURL,
+ type: Number(notificationTarget),
+ },
+ })
+ } else {
+ // Others (Telegram)
+ setUserProfile({
+ ...userProfile,
+ notification_target: {
+ target: chatID,
+ type: Number(notificationTarget),
+ },
+ })
+ }
+
alert('Notification target updated')
})
}
@@ -323,7 +347,7 @@ const NotificationSetting = () => {
Custom Notification
- Notificaiton through other platform like Telegram or Pushover
+ Notification through other platform like Telegram or Pushover
@@ -380,6 +404,7 @@ const NotificationSetting = () => {
+
{notificationTarget === '1' && (
@@ -438,6 +463,19 @@ const NotificationSetting = () => {
/>
>
)}
+ {notificationTarget === '4' && (
+ <>
+ Webhook URL
+ setWebhookURL(e.target.value)}
+ placeholder='Webhook URL'
+ sx={{
+ width: '200px',
+ }}
+ />
+ >
+ )}
{error && (
{error}
diff --git a/src/views/components/AddTaskModal.jsx b/src/views/components/AddTaskModal.jsx
index 99cfa68..5438fd6 100644
--- a/src/views/components/AddTaskModal.jsx
+++ b/src/views/components/AddTaskModal.jsx
@@ -430,12 +430,18 @@ const TaskInput = ({ autoFocus, onChoreUpdate }) => {
assignedTo: userProfile.id,
assignStrategy: 'random',
isRolling: false,
- notification: false,
+ notification: true,
description: description || null,
labelsV2: [],
priority: priority || 0,
status: 0,
frequencyType: 'once',
+ frequencyMetadata: {},
+ notificationMetadata: {
+ dueDate: true,
+ predue: true,
+ nagging: true
+ },
}
if (frequency) {
diff --git a/src/views/components/NavBar.jsx b/src/views/components/NavBar.jsx
index 86f8e9c..40fa01d 100644
--- a/src/views/components/NavBar.jsx
+++ b/src/views/components/NavBar.jsx
@@ -105,12 +105,12 @@ const NavBar = () => {
}
return (
-