Compare commits

..

19 commits

Author SHA1 Message Date
6bba18e208
feat: discord notifications & notify by default
Some checks are pending
Build validation / build (20.x) (push) Waiting to run
Build validation / build (22.x) (push) Waiting to run
Build validation / lint (push) Waiting to run
2025-04-04 23:32:59 +01:00
Mo Tarbin
36faaf0b97 Merge branch 'dev' 2025-03-17 01:03:05 -04:00
Mo Tarbin
e56893fd64 Add frequency and notification metadata to task input; update NavBar styles for improved layout 2025-03-17 01:02:52 -04:00
Mo Tarbin
c553e2077a Merge branch 'main' of https://github.com/Donetick/frontend 2025-03-05 22:06:09 -05:00
Mo Tarbin
76ed927839 Merge branch 'dev' 2025-03-05 22:04:36 -05:00
Mo Tarbin
08de84a889 Fix optional chaining for userProfile in chore filters and adjust data destructuring in MyChores component 2025-03-05 22:04:28 -05:00
Mo Tarbin
52b81cc115 Refactor MyChores component to include chore filters in ChoresGrouper calls 2025-03-05 21:41:32 -05:00
Mo Tarbin
de08c6c5b1 Enhance ChoresGrouper to include chore filters and update localStorage handling for selectedChoreFilter 2025-03-05 21:40:47 -05:00
Mo Tarbin
ee566bd2a3 Merge branch 'dev' 2025-03-05 21:06:32 -05:00
Mo Tarbin
9d8aec1aa6 Fix localStorage retrieval for selectedChoreFilter in MyChores component 2025-03-05 21:06:22 -05:00
Mohamad Tarbin
9dc8fe77bc
Update README.md 2025-03-05 19:49:00 -05:00
Mohamad Tarbin
409851a64c
Merge pull request #18 from ukr01/feature/SSO-Entropy-Fix
Adds 8 chars to state in call to custom auth provider instead of 6
2025-03-05 19:46:23 -05:00
Mo Tarbin
cd1b556fb1 Merge branch 'dev' 2025-03-05 19:45:38 -05:00
Mo Tarbin
467fc935f0 - Support nest sub tasks
- Support filters in ChoreGrouper
- completion window only available if due date selected
- Add SortAndGrouping Component : support Filter by Assignee
- update Notification Switch to align left of the text
- Support caching filters
2025-03-05 19:43:43 -05:00
Ulrik Kristensen
a98f9c698b fixes randomState to have 8 chars instead of 6 2025-03-03 13:20:08 +01:00
Mo Tarbin
6431e7145d Bump version to 0.1.95 and add dnd-kit dependencies; implement CompleteSubTask function and enhance chore sorting logic 2025-02-25 23:40:26 -05:00
Mo Tarbin
47348190ab Merge branch 'dev' 2025-02-15 00:05:51 -05:00
Mo Tarbin
cbf99fad18 Fix https://github.com/donetick/donetick/issues/128 2025-02-15 00:05:44 -05:00
Mo Tarbin
c06d5f1f30 Merge branch 'main' of https://github.com/meauxt/donetick-frontend 2025-02-12 22:26:32 -05:00
11 changed files with 200 additions and 27 deletions

19
.direnv/bin/nix-direnv-reload Executable file
View file

@ -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

1
.envrc Normal file
View file

@ -0,0 +1 @@
use flake

View file

@ -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

61
flake.lock generated Normal file
View file

@ -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
}

23
flake.nix Normal file
View file

@ -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 = {
};
};
}

View file

@ -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
},
})

View file

@ -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

View file

@ -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(

View file

@ -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,6 +145,17 @@ const NotificationSetting = () => {
return
}
// Discord
if (notificationTarget === '4') {
setUserProfile({
...userProfile,
notification_target: {
target: webhookURL,
type: Number(notificationTarget),
},
})
} else {
// Others (Telegram)
setUserProfile({
...userProfile,
notification_target: {
@ -141,6 +163,8 @@ const NotificationSetting = () => {
type: Number(notificationTarget),
},
})
}
alert('Notification target updated')
})
}
@ -323,7 +347,7 @@ const NotificationSetting = () => {
<Typography level='h3'>Custom Notification</Typography>
<Divider />
<Typography level='body-md'>
Notificaiton through other platform like Telegram or Pushover
Notification through other platform like Telegram or Pushover
</Typography>
<FormControl orientation='horizontal'>
@ -380,6 +404,7 @@ const NotificationSetting = () => {
<Option value='0'>None</Option>
<Option value='1'>Telegram</Option>
<Option value='2'>Pushover</Option>
<Option value='4'>Discord</Option>
<Option value='3'>Webhooks</Option>
</Select>
{notificationTarget === '1' && (
@ -438,6 +463,19 @@ const NotificationSetting = () => {
/>
</>
)}
{notificationTarget === '4' && (
<>
<Typography level='body-sm'>Webhook URL</Typography>
<Input
value={webhookURL}
onChange={e => setWebhookURL(e.target.value)}
placeholder='Webhook URL'
sx={{
width: '200px',
}}
/>
</>
)}
{error && (
<Typography color='warning' level='body-sm'>
{error}

View file

@ -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) {

View file

@ -105,12 +105,12 @@ const NavBar = () => {
}
return (
<nav className='flex gap-2 p-2'>
<IconButton size='sm' variant='plain' onClick={() => setDrawerOpen(true)}>
<nav className='flex gap-2 p-3'>
<IconButton size='md' variant='plain' onClick={() => setDrawerOpen(true)}>
<MenuRounded />
</IconButton>
<Box
className='flex items-center gap-1'
className='flex items-center gap-2'
onClick={() => {
navigate('/my/chores')
}}