Compare commits
19 commits
d9de502894
...
6bba18e208
Author | SHA1 | Date | |
---|---|---|---|
6bba18e208 | |||
![]() |
36faaf0b97 | ||
![]() |
e56893fd64 | ||
![]() |
c553e2077a | ||
![]() |
76ed927839 | ||
![]() |
08de84a889 | ||
![]() |
52b81cc115 | ||
![]() |
de08c6c5b1 | ||
![]() |
ee566bd2a3 | ||
![]() |
9d8aec1aa6 | ||
![]() |
9dc8fe77bc | ||
![]() |
409851a64c | ||
![]() |
cd1b556fb1 | ||
![]() |
467fc935f0 | ||
![]() |
a98f9c698b | ||
![]() |
6431e7145d | ||
![]() |
47348190ab | ||
![]() |
cbf99fad18 | ||
![]() |
c06d5f1f30 |
11 changed files with 200 additions and 27 deletions
19
.direnv/bin/nix-direnv-reload
Executable file
19
.direnv/bin/nix-direnv-reload
Executable 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
1
.envrc
Normal file
|
@ -0,0 +1 @@
|
|||
use flake
|
|
@ -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
61
flake.lock
generated
Normal 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
23
flake.nix
Normal 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 = {
|
||||
};
|
||||
};
|
||||
}
|
|
@ -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
|
||||
},
|
||||
})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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')
|
||||
}}
|
||||
|
|
Loading…
Add table
Reference in a new issue