Compare commits

...

492 Commits

Author SHA1 Message Date
9aa8e7edff Merge pull request 'first merge to main 🚀' (#71) from dev into main
Reviewed-on: #71
Reviewed-by: Nicolai Ort <info@nicolai-ort.com>
2021-02-19 17:03:02 +00:00
3834079481 new license file version [CI SKIP] 2021-02-18 16:17:27 +00:00
9faa93e292 Merge pull request 'component/ structure cleanup feature/68-component-cleanup' (#70) from feature/68-component-cleanup into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #70
2021-02-18 16:16:43 +00:00
27609dc5e0 Removed debug output
ref #68
2021-02-18 17:13:58 +01:00
2b57d49e4e Dependency bump 👊
ref #68
2021-02-18 17:13:19 +01:00
dc0c738471 Added license to package
ref #68
2021-02-18 17:08:19 +01:00
ca41f4d4f2 Removed filepond
ref #68
2021-02-18 17:06:54 +01:00
e1427f3ecb renamed folder and removed useless files
ref #68
2021-02-18 17:06:25 +01:00
e2fb9a66ad Reimported simple.css
ref #68
2021-02-18 17:04:39 +01:00
7278648642 Removed usless console logs
ref #68
2021-02-18 17:03:05 +01:00
a4c955ce85 Fixed org deletion dialog 2021-02-18 17:01:08 +01:00
f086027910 Fixed store destrucuured import
ref #68
2021-02-18 16:57:05 +01:00
543b3bd937 Fixed store not being found
ref #68
2021-02-18 16:56:49 +01:00
c0534a3b06 Initial component sort/cleanup
ref #68
2021-02-18 16:51:15 +01:00
0c9785af36 new license file version [CI SKIP] 2021-02-18 15:14:23 +00:00
482149139a Merge pull request 'feature/50-contact-management' (#67) from feature/50-contact-management into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #67
close #50
2021-02-18 15:13:34 +00:00
c89038f337 Merge branch 'dev' into feature/50-contact-management
ref #50
2021-02-18 16:11:49 +01:00
696d3ffabf Fixed contact update detection bug
ref #50
2021-02-18 16:05:20 +01:00
14e5d0e740 Fixed address update bug
ref #50
2021-02-18 15:45:38 +01:00
e4ae1dd475 Fixed group posting issue
ref #50
2021-02-17 20:23:30 +01:00
46cd262fab Fixed modal multiselect
ref #50
2021-02-17 20:15:11 +01:00
3f5418083f Merge pull request 'Translated everything feature/65-translations' (#66) from feature/65-translations into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #66
2021-02-17 18:52:14 +00:00
1586c2f9e6 OrgDetail - edit contact
ref #50
2021-02-17 19:51:04 +01:00
e64b318a42 Sorted locales
ref #65
2021-02-17 19:46:13 +01:00
2033572c83 TeamDetail - edit contact
ref #50
2021-02-17 19:45:37 +01:00
ce678c1b76 Gendered some stuff
ref #65
2021-02-17 19:45:25 +01:00
83495b101c German spell check
ref #65
2021-02-17 19:43:16 +01:00
6c2a5f904d Now w/ 100% german translation 🌍
ref #65
2021-02-17 19:39:19 +01:00
c1251d3332 🌎 Contacts i18n
ref #50
2021-02-17 19:15:05 +01:00
4ef1b7abe8 🎉 ContactDetail + ContactOverview
ref #50
2021-02-17 19:02:35 +01:00
d822e4ab3f Fixed import modal width
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-16 17:25:57 +01:00
b01fe050d2 🔗 link to ContactDetail from TeamsOverview
ref #50
2021-02-15 20:54:09 +01:00
1a4cf211eb 🔗 link to ContactDetail from OrgOverview
ref #50
2021-02-15 20:53:23 +01:00
4541304fa8 🚧 WIP on ContactDetail
ref #50
2021-02-15 20:50:15 +01:00
894160f3f7 ContactDetail - added checkbox for optional address
ref #50
2021-02-15 18:19:25 +01:00
6a91bd53e2 🐞 fixed null addresses in ContactsOverview
ref #50
2021-02-15 17:52:20 +01:00
0f013304ef 🧹 ContactOverview refinement
ref #50
2021-02-15 17:46:01 +01:00
6f4f4ccb16 ContactDetail route
ref #50
2021-02-15 17:45:41 +01:00
7138ca1f5f AddContactModal - allow optional address
ref #50
2021-02-15 17:45:20 +01:00
45e7f6a0d1 🎉 working AddContactModal
ref #50
2021-02-13 20:30:38 +01:00
a7098df9cf ContactsEmptyState
ref #50
2021-02-13 20:13:05 +01:00
054c7faaac basic Contact components
ref #50
2021-02-13 19:26:38 +01:00
087c85e586 🚀RELEASE v0.6.0
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-12 19:18:33 +01:00
43df188df1 new license file version [CI SKIP] 2021-02-12 18:16:52 +00:00
1d40c6d068 Merge branch 'dev' of https://git.odit.services/lfk/frontend into dev
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-12 19:15:41 +01:00
6614242df6 dependency bump 2021-02-12 19:15:34 +01:00
b25dc623cf new license file version [CI SKIP] 2021-02-12 18:10:08 +00:00
9d3bb4e4e3 Merge pull request 'feature/52-runner-filters' (#63) from feature/52-runner-filters into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #63
close #52
2021-02-12 18:09:21 +00:00
5dc11c285d RunnersOverview - support should_display_based_on_id search
ref #52
2021-02-12 19:06:31 +01:00
575b4ce970 RunnersOverview - basic working filter
ref #52
2021-02-12 18:47:46 +01:00
e23821a7cb WIP on filter
ref #52
2021-02-12 18:04:10 +01:00
e415258787 basic select filtering in RunnersOverview
ref #52
2021-02-09 19:22:20 +01:00
1451991d03 Merge pull request 'feature/43-password-reset' (#61) from feature/43-password-reset into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #61
close #43
2021-02-07 16:11:16 +00:00
92fee08dc4 ⏮decode base64 reset key 2021-02-07 13:26:57 +01:00
7b7e484091 🧹 formatting 2021-02-07 13:25:39 +01:00
e7291a31f3 Merge pull request 'feature/51-teamoverview-badge-org' (#59) from feature/51-teamoverview-badge-org into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #59
close #51
2021-02-07 12:12:57 +00:00
2dc31912cc Merge pull request 'feature/47-sidebar-responsiveness' (#60) from feature/47-sidebar-responsiveness into dev
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #60
close #47
2021-02-07 12:12:29 +00:00
428a8a10ff 🌍 i18n for ResetPassword
ref #43
2021-02-07 13:11:08 +01:00
8b2f1965e2 👀 ResetPassword - success and error states
ref #43
2021-02-07 13:08:44 +01:00
b18a99e4df added basic UI for ResetPassword
ref #43
2021-02-07 12:51:21 +01:00
1fac0c8640 😬 use actual team id for RunnersOverview badge
ref #51
2021-02-07 12:18:34 +01:00
f0be73c2cd Merge commit '65f49489ad2e0cff30560e4c326ca7294d7f6190' into feature/51-teamoverview-badge-org 2021-02-07 12:11:09 +01:00
42bd632539 👀 only display navbar on sm devices / hide on md and up 2021-02-07 12:07:25 +01:00
f8014c6213 🐞 re-add sidebar component 2021-02-07 12:06:54 +01:00
65f49489ad new license file version [CI SKIP] 2021-02-07 11:05:54 +00:00
e9e24d5f2d Merge branch 'dev' of https://git.odit.services/lfk/frontend into dev
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-07 12:05:08 +01:00
369b09972c general dependency bump 2021-02-07 12:04:55 +01:00
e4e2bdac72 new license file version [CI SKIP] 2021-01-30 16:58:42 +00:00
ec0db39184 Merge pull request 'feature/15-runner-import' (#58) from feature/15-runner-import into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #58
close #15
2021-01-30 16:57:59 +00:00
a02be78df5 RunnersOverview badge to team/org
ref #51
2021-01-30 17:56:58 +01:00
3490abe9a7 Org badge in TeamsOverview
ref #51
2021-01-30 17:56:40 +01:00
b5013426e6 reactivity in import table
ref #15
2021-01-30 17:50:16 +01:00
7dd0881d29 working import binding
ref #15
2021-01-30 17:45:12 +01:00
acf0562851 ImportRunnerModal - differenciate between team and org import
ref #15
2021-01-30 17:27:51 +01:00
80c3a90d6f client library bump
ref #15
2021-01-30 17:14:20 +01:00
c6985087a8 layout for Import from RunnerOverview
ref #15
2021-01-30 17:05:19 +01:00
ef50e2d5ce Merge pull request 'feature/56-footer-version-loading' (#57) from feature/56-footer-version-loading into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #57
close #56
2021-01-30 15:33:24 +00:00
ed1dc6e8d5 Merge branch 'dev' into feature/56-footer-version-loading 2021-01-30 15:28:07 +00:00
c2d7a319a0 use onMount event instead of DOMready
ref #56
2021-01-30 16:24:48 +01:00
0e04298b7c new license file version [CI SKIP] 2021-01-30 15:17:37 +00:00
3a5a73b02c Merge pull request 'feature/46-imprint-privacy' (#55) from feature/46-imprint-privacy into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #55
close #46
2021-01-30 15:16:46 +00:00
901c7c1241 load version on DOMContentLoaded
ref #56
2021-01-30 16:16:30 +01:00
4e82369b16 text selection color
ref #46
2021-01-30 16:14:38 +01:00
945963db32 Footer linking
ref #46
2021-01-29 19:07:46 +01:00
5741cbe756 added Privacy page
ref #46
2021-01-29 19:02:53 +01:00
6401aeb3a8 working Imprint page
ref #46
2021-01-29 18:49:02 +01:00
6a0c129d39 added basic styling to Imprint component
ref #46
2021-01-29 17:58:52 +01:00
bbec9ffcdf added Imprint route /imprint
ref #46
2021-01-29 17:58:22 +01:00
541f1fa2e3 Merge pull request 'feature/44-require-mail-addresses' (#54) from feature/44-require-mail-addresses into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #54
close #44
2021-01-29 15:38:06 +00:00
7897820632 UserDetail - invalid email UI feedback
ref #44
2021-01-29 16:34:55 +01:00
0dd9de2abc UserDetail - enforce email input
ref #44
2021-01-27 18:48:23 +01:00
7131ba99b6 AddUserModal - enforce email input
ref #44
2021-01-27 18:45:36 +01:00
c69026aa5b Merge pull request 'feature/45-component-drop' (#53) from feature/45-component-drop into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #53
close #45
2021-01-27 17:42:08 +00:00
16ac96c64e 🧹 remove placeholder options from Dashboard sidebar
ref #45
2021-01-27 18:26:46 +01:00
1bb79b1c98 🧹 remove ComponentDump from MainDashContent
ref #45
2021-01-27 18:26:26 +01:00
eeee272f03 🧹 drop old + unused components Dash + LoginAlt
ref #45
2021-01-27 18:26:04 +01:00
2563e9d1d6 🚀RELEASE v0.5.0
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-26 18:29:39 +01:00
24bec66390 🐞 fix package release for index template compatibility 2021-01-26 18:29:26 +01:00
3d30734dc2 new license file version [CI SKIP] 2021-01-26 17:26:12 +00:00
cbc1d53cc2 🐞 fixed merge conflict errors
All checks were successful
continuous-integration/drone/push Build is passing
ref #13 #12
2021-01-26 18:25:30 +01:00
9a1f7a13f8 general dependency bump 2021-01-26 18:25:02 +01:00
d48b9b7f91 Merge pull request 'feature/12-user-management' (#39) from feature/12-user-management into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #39
close #12
2021-01-26 16:28:59 +00:00
358865dc6a UserDetail - update permission badges on change save
ref #12
2021-01-26 17:28:07 +01:00
e750c37473 🌎 UserDetail - more i18n keys
ref #12
2021-01-26 17:27:44 +01:00
71a589c10c Merge branch 'dev' into feature/12-user-management
# Conflicts:
#	src/App.svelte
2021-01-26 17:00:21 +01:00
3281239ff5 new license file version [CI SKIP] 2021-01-26 15:59:14 +00:00
3e2cdddd5a Merge pull request 'feature/13-runner-management' (#42) from feature/13-runner-management into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #42
close #13
2021-01-26 15:58:28 +00:00
1d4c3e51c7 Merge branch 'dev' into feature/13-runner-management 2021-01-26 15:57:55 +00:00
1694c71528 AddRunnerModal - improved team select
ref #13
2021-01-26 16:54:15 +01:00
169ffc1b0b 🌎 added more i18n keys
ref #13
2021-01-26 16:53:59 +01:00
89eb3259d4 Merge pull request 'added permissions to dashboard sidebar' (#41) from feature/40-dynamic-sidebar-options into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #41
close #41
2021-01-26 15:42:02 +00:00
ffd88ffc66 🐞 alphabetically sort permission targets in UserDetail
ref #39 ref #12
2021-01-25 20:56:20 +01:00
6c2d13bd17 🌎 last i18n keys
ref #13
2021-01-25 20:39:34 +01:00
8d92a75ef0 🏃‍♂️ support for runner group edit
ref #13
2021-01-25 20:39:22 +01:00
acb86ae266 added permissions to dashboard sidebar
ref #40
2021-01-25 20:20:58 +01:00
473cf978b4 Merge branch 'dev' into feature/12-user-management
# Conflicts:
#	src/components/UserDetail.svelte
2021-01-24 21:50:38 +01:00
4debab2636 Merge branch 'dev' into feature/13-runner-management
# Conflicts:
#	package.json
2021-01-24 20:39:11 +01:00
e91e197873 new license file version [CI SKIP] 2021-01-24 19:39:09 +00:00
b30b6734a1 general dependency bump
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-24 20:38:29 +01:00
dadb80608a 💣 process breaking changes for lib v0.3.0
Some checks failed
continuous-integration/drone/push Build is failing
2021-01-24 20:37:56 +01:00
16c9969fb9 Merge branch 'feature/13-runner-management' of https://git.odit.services/lfk/frontend into feature/13-runner-management 2021-01-24 18:41:04 +01:00
dc1644ed25 🐞 ImportRunnerModal - table overflow fix
ref #13
2021-01-24 18:40:54 +01:00
2cc9b3e1ed 👀 ImportRunnerModal - layout cleanup
ref #13
2021-01-24 18:38:16 +01:00
415f340a68 i18n 🌍
ref #13
2021-01-24 18:20:48 +01:00
2d4869128d 🌎 i18n
ref #13
2021-01-24 17:54:45 +01:00
ae8bc01d9b 🌎 i18n
ref #13
2021-01-24 17:47:29 +01:00
e2552d9442 Merge branch 'dev' into feature/13-runner-management 2021-01-24 17:41:11 +01:00
5d1b5d80b6 Merge branch 'feature/13-runner-management' of https://git.odit.services/lfk/frontend into feature/13-runner-management 2021-01-24 17:31:31 +01:00
366804aa29 🌎 i18n
ref #13
2021-01-24 17:31:10 +01:00
9240e0c903 🧹 general runner cleanup
ref #13
2021-01-24 17:30:31 +01:00
7baaf2cff3 Merge branch 'feature/13-runner-management' of git.odit.services:lfk/frontend into feature/13-runner-management 2021-01-24 17:23:18 +01:00
9fbc1ba031 Added friles to ignore 2021-01-24 17:21:48 +01:00
6704c07db0 Deleted files to ignore 2021-01-24 17:21:39 +01:00
a87165148a 🧹 RunnerDetail cleanup + i18n 🌎
ref #13
2021-01-24 17:21:19 +01:00
ec4bcd093b 🐞 [bugfix] RunnerDetail update
ref #13
2021-01-24 17:16:49 +01:00
5552055b98 RunnerDetail - button text fixes
ref #13
2021-01-21 20:50:14 +01:00
03aa67034d basic RunnerDetail
ref #13
2021-01-21 19:07:43 +01:00
fc21427685 💻 updated VSCode recommended extensions
All checks were successful
continuous-integration/drone/push Build is passing
change i18n-ally scoping
2021-01-21 18:41:05 +01:00
819b02a204 ImportRunnerModal - hide team when loading from TeamDetail
ref #13
2021-01-20 20:25:41 +01:00
de0bd5fd57 ImportRunnerModal usage in TeamDetail
ref #13
2021-01-20 20:21:59 +01:00
8aa1d94a1a use ImportRunnerModal in OrgDetail + Orgs
ref #13
2021-01-20 19:59:53 +01:00
f8a59133a2 ImportRunnerModal - compatibility for multi-component access
ref #13
2021-01-20 19:59:31 +01:00
c382f770dc 👀 basic ui interaction for ImportRunnerModal
ref #13
2021-01-20 18:31:23 +01:00
cde4ec13ef basic cancel event dispatch from ImportRunnerModal
ref #13
2021-01-20 18:15:47 +01:00
ecad1ea839 ↙ added default fallback parsing to ImportRunnerModal
ref #13
2021-01-20 17:56:32 +01:00
6b91b22713 🚀 ImportRunnerModal - working demo
ref #13
2021-01-20 17:46:18 +01:00
e34c91b2cc 🌎 i18n in ImportRunnerModal headers
ref #13
2021-01-20 17:33:10 +01:00
822759a688 😦 added missing dependencies
ref #13
2021-01-19 21:45:00 +01:00
b606037890 🌎 added locale based csv/xlsx header parsing
ref #13
2021-01-19 21:44:41 +01:00
74d9b94119 basic xlsx + csv parsing
ref #13
2021-01-19 21:44:13 +01:00
b1e9f955b0 basic ImportRunnerModal with CSV input
ref #13
2021-01-19 18:19:21 +01:00
e0356fa360 fixed runner permissions
ref #13
2021-01-19 18:18:51 +01:00
fbc67eeb98 Merge branch 'dev' of https://git.odit.services/lfk/frontend into dev 2021-01-18 20:34:50 +01:00
09fd73b130 🐞 improved version builder from template 2021-01-18 20:34:47 +01:00
259a76f46b 🧹 darkmode classes cleanup 2021-01-18 20:29:44 +01:00
c6504c2eaf new license file version [CI SKIP] 2021-01-17 18:46:15 +00:00
7d104a1514 🚀RELEASE v0.4.0
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-17 19:45:28 +01:00
b3bd61c89e Merge branch 'feature/13-runner-management' into dev
Some checks reported errors
continuous-integration/drone/push Build was killed
2021-01-17 19:44:07 +01:00
e49dca0275 🐞 gitignore fix 2021-01-17 19:29:05 +01:00
03125b3a2d general dependency bumps 2021-01-17 19:24:57 +01:00
a523379b3a gitignore fix 2021-01-17 19:24:25 +01:00
aa6348a29a fix package:dev script
All checks were successful
continuous-integration/drone/push Build is passing
ref #37
2021-01-17 19:22:45 +01:00
b9f0f1a69a Merge commit 'a284806d3cb769030a4e28d0403190b746f8fc61' into dev
All checks were successful
continuous-integration/drone/push Build is passing
close #37
2021-01-17 19:18:42 +01:00
a284806d3c re-enable PWA functionality via serviceworker
ref #37
2021-01-17 19:15:22 +01:00
7e10c1db65 🔨 cleaned up build process + Dockerfile
ref #37
2021-01-17 19:14:14 +01:00
11790638d6 🔨 cleaned up build process + Dockerfile
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-17 19:01:11 +01:00
0583cbe266 improved serviceworker + PWA logic
Some checks reported errors
continuous-integration/drone/push Build was killed
2021-01-17 18:59:26 +01:00
2e6874c822 apply new gitignore config 2021-01-17 18:32:32 +01:00
2ce41990bf 🐞 fix cross-env logic for faster dev starts 2021-01-17 18:31:35 +01:00
c8aeba38ba Merge branch 'dev' of https://git.odit.services/lfk/frontend into dev
Some checks failed
continuous-integration/drone/push Build is failing
2021-01-17 18:22:57 +01:00
5e02502a5c 👩‍💻 developer configs/ recommendations for VSCode 2021-01-17 18:22:28 +01:00
382cc3d844 new license file version [CI SKIP] 2021-01-17 17:19:48 +00:00
dd74d9ee89 🧹 gitignore changes in public/index.html & svelte.config.js
Some checks failed
continuous-integration/drone/push Build is failing
2021-01-17 18:19:07 +01:00
383f82807f improved dev scripts for speed starts
Some checks reported errors
continuous-integration/drone/push Build was killed
2021-01-17 18:16:02 +01:00
d4579a9a41 dynamic contact info in AddRunnerModal
ref #13
2021-01-17 17:54:21 +01:00
66a07c6a51 RunnersOverview
ref #13
2021-01-17 17:51:03 +01:00
66ffd8e936 AddRunnerModal
ref #13
2021-01-17 17:50:52 +01:00
44f07ca231 🧹 AddUserModal cleanup 2021-01-17 17:23:26 +01:00
ff14f024af AddUserModal - username/email validation
ref #12
2021-01-17 17:14:34 +01:00
dccf7c6c8d PWA optimizations
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-17 15:59:28 +01:00
10d7955f99 UserPermissions - working edit
ref #12
2021-01-16 21:42:09 +01:00
64ade901de 🚀RELEASE v0.3.1
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-16 20:59:45 +01:00
eb0dd3f781 🤝 attribution/ credits for icons and illustrations
Some checks reported errors
continuous-integration/drone/push Build was killed
2021-01-16 20:59:08 +01:00
66e6cd80d3 added new empty states
Some checks reported errors
continuous-integration/drone/push Build was killed
2021-01-16 20:49:29 +01:00
959b32de1c new UsersEmptyState
ref #12
2021-01-16 20:48:45 +01:00
9c6dc5b424 basic UserPermissions view
ref #12
2021-01-16 20:33:34 +01:00
aaab95d414 UserDetail - link to permission page
ref #12
2021-01-16 18:30:35 +01:00
7d4e93912c UserDetail - basic permission badges
ref #12
2021-01-16 18:23:28 +01:00
d65b463547 UserDetail - add permission layout
ref #12
2021-01-16 17:31:17 +01:00
6e5a4bcb39 formatting
ref #12
2021-01-16 17:19:03 +01:00
1a799dc30a Fixed User group update
ref #12
2021-01-16 17:17:56 +01:00
e3d2676858 Merge branch 'dev' into feature/12-user-management 2021-01-16 17:11:10 +01:00
42851686ca Merge pull request 'feature/14-team-management' (#36) from feature/14-team-management into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #36
closee #14
2021-01-16 15:29:47 +00:00
e3943d868a 🐞 fix deletion in TeamDetail + TeamsOverview
ref #14
2021-01-16 16:27:06 +01:00
7654b795c7 🧹 TeamDetail cleanup
ref #14
2021-01-16 16:21:56 +01:00
489244f1a9 🔒 ConfirmTeamDeletion in TeamDetail
ref #14
2021-01-15 23:05:36 +01:00
cbcce336d6 🔒 ConfirmTeamDeletion in TeamsOverview
ref #14
2021-01-15 23:02:40 +01:00
52a96b2a4f Merge pull request 'feature/16-org-management' (#35) from feature/16-org-management into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #35
close #16
2021-01-15 21:55:48 +00:00
ce6002a631 🔒 re-enable confirmation in OrgOverview
ref #16
2021-01-15 22:50:56 +01:00
84a9cf069a UX - ConfirmOrgDeletion cancel event reflection in datatable
ref #16
2021-01-15 22:49:16 +01:00
83f19a7572 🔒 ConfirmOrgDeletion in OrgOverview
ref #16
2021-01-15 22:35:40 +01:00
a1a4c8b56d Merge branch 'feature/14-team-management' into feature/16-org-management 2021-01-15 22:31:56 +01:00
d8901126d0 🔒 ConfirmOrgDeletion in OrgDetail
ref #16
2021-01-15 22:30:38 +01:00
854db4ece8 🧹 drop tmp modification from UsersOverview
ref #14
2021-01-15 22:05:39 +01:00
07f2e65fc7 🧹 Team cleanups
ref #14
2021-01-15 22:04:21 +01:00
ccf09f97d5 TeamDetail with edit,delete
ref #14
2021-01-15 22:01:43 +01:00
8f9a4ebc04 Merge branch 'dev' into feature/14-team-management 2021-01-15 21:48:34 +01:00
f1833f13d5 🧹 Dashboard - drop header bar
Some checks reported errors
continuous-integration/drone/push Build was killed
2021-01-15 21:48:11 +01:00
6a81e369fa 🐞 fix Dashboard sidebar responsiveness
Some checks reported errors
continuous-integration/drone/push Build was killed
drop entire js logic - css only
2021-01-15 21:42:17 +01:00
597e9e1ea9 basic TeamsOverview
ref #14
2021-01-15 21:22:51 +01:00
9bb027ec4c AddTeamModal working
ref #14
2021-01-15 21:22:37 +01:00
fbbbaa5d49 Merge branch 'dev' into feature/14-team-management 2021-01-15 20:37:30 +01:00
aaec5a3fc9 new license file version [CI SKIP] 2021-01-15 19:21:12 +00:00
7cd24cd51d 🚀RELEASE v0.3.0
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-15 20:20:02 +01:00
c81b34c1d0 Merge branch 'dev' of https://git.odit.services/lfk/frontend into dev
Some checks reported errors
continuous-integration/drone/push Build was killed
2021-01-15 20:19:03 +01:00
7b1acc494d Merge pull request 'feature/16-org-management' (#32) from feature/16-org-management into dev
Some checks reported errors
continuous-integration/drone/push Build was killed
Reviewed-on: #32
 close #16
2021-01-15 19:18:52 +00:00
6ff90694e2 bump gridjs to 3.2.2 2021-01-15 20:17:36 +01:00
157c7c66b5 🧹 general component cleanup
Some checks reported errors
continuous-integration/drone/push Build was killed
2021-01-15 20:14:54 +01:00
93249258c6 🏁 finish basic functionality of AddOrgModal + OrgDetail
ref #16
2021-01-15 20:03:29 +01:00
01c01a46fa 🌎 i18n
ref #16
2021-01-15 19:25:42 +01:00
0e2a10fe94 basic functionality in OrgDetail
ref #16
2021-01-15 19:25:30 +01:00
0b9f3de47c improvements in OrgOverview
ref #16
2021-01-15 19:25:12 +01:00
bc239eead1 💬 AddOrgModal bindings
ref #16
2021-01-15 19:24:46 +01:00
7a09869b0c 🏬 OrgDetail ui
ref #16
2021-01-15 19:14:46 +01:00
bdc0de6ada added Org base components
ref #16
2021-01-15 18:49:45 +01:00
6870a7f9b1 🧹 TeamsOverview - formatting
ref #14
2021-01-15 17:51:12 +01:00
ace1a1b063 🐞 fix component mount in TeamsEmptyState
ref #14
2021-01-15 17:11:04 +01:00
d87b879cc3 🏃‍♂️🏃‍♂️🏃‍♂️ basic UI components for team management
ref #14
2021-01-14 19:10:43 +01:00
65b36f8e69 🙋‍♂️🔒 UserDetail - permission layout ui
ref #12
2021-01-14 18:56:28 +01:00
87387a54f2 Merge branch 'dev' into feature/12-user-management 2021-01-14 18:30:08 +01:00
b497cebe76 Merge commit 'fcd657c10ea14290455cfb0bf2de89375a664143' into dev
All checks were successful
continuous-integration/drone/push Build is passing
close #31
2021-01-14 18:26:33 +01:00
0fa107a75b [tmp] - disable serviceworker 2021-01-14 18:25:01 +01:00
b34e3aeed0 👀 UsersOverview - disable advanced search
ref #12
2021-01-14 18:24:37 +01:00
35b18d72fd Merge branch 'dev' of https://git.odit.services/lfk/frontend into dev
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-14 18:22:54 +01:00
4b80f30afb 🐳 Dockerfile - drop js sourcemaps 2021-01-14 18:22:33 +01:00
86c54e04a8 🙋‍♂️ UserDetail - active/inactive user state edit
ref #12
2021-01-14 18:22:09 +01:00
ef9fc596f5 🙋‍♂️ UserDetail - disable profile picture edit
ref #12
2021-01-14 18:21:48 +01:00
ad34e455ce new license file version [CI SKIP] 2021-01-14 17:18:21 +00:00
01fdd0bee2 Bump Dockerfile builder to 15.5.1-alpine3.12
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-14 18:17:29 +01:00
32ffa345cd 🔨 config compatibility for new Snowpack V3 bundler
Some checks failed
continuous-integration/drone/push Build is failing
2021-01-14 18:17:12 +01:00
6fc3c16073 basic dependency bump
Some checks failed
continuous-integration/drone/push Build is failing
lfk-library, snowpack@3.0.10 & svelte plugin
2021-01-14 18:16:46 +01:00
7d58657c80 🔨 reorder CI build order for correct license exporting
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-13 21:38:05 +01:00
fcd657c10e 🐞 fix sidebar mobile-md scaling
ref #31
2021-01-13 21:37:29 +01:00
4ab77c5557 Merge branch 'dev' of https://git.odit.services/lfk/frontend into dev
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-13 21:07:08 +01:00
2bbaa500f4 🚀RELEASE v0.2.1 2021-01-13 21:06:51 +01:00
722a20e141 🐞 fix package release script: locales directory 2021-01-13 21:06:39 +01:00
041c24a837 🙋‍♂️ UserDetails - group updating
ref #12
2021-01-13 21:05:03 +01:00
39a3baa00b 🐞 UserDetail - fix permission reactivity by assignments
ref #12
2021-01-13 18:17:34 +01:00
f7acbb1eaa shared state reactivity - AddUserModal-Users-UsersOverview
ref #12
2021-01-13 17:49:01 +01:00
e6fbf7aa5b UserDetail - fixed group updating
ref #12
2021-01-12 21:19:12 +01:00
87926e69db new license file version [CI SKIP] 2021-01-12 20:07:09 +00:00
36a084eab6 🔒 UserDetail - WIP on Permissions
ref #12
2021-01-12 21:04:12 +01:00
a9e319e0c0 👪 UserDetail - group edit support
ref #12
2021-01-12 20:16:16 +01:00
ea23b97231 💬 UserDetail - info Toasts
ref #12
2021-01-12 19:30:54 +01:00
7df76f9642 AddUserModal + UserDetail - optional username field
ref #12
2021-01-12 19:30:06 +01:00
f6db117a5e bump @odit/lfk-client-js to 0.0.11
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-11 21:21:49 +01:00
23c3cd605d 🔨 optimized release script 2021-01-11 21:21:12 +01:00
77690702c0 🚀RELEASE v0.2.0
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-11 21:17:35 +01:00
e0093480d9 Merge branch 'feature/12-user-management' into dev 2021-01-11 21:17:12 +01:00
c7679b7a67 [tmp] - disable darkmode + re-enable sw 2021-01-11 21:17:05 +01:00
e6ac34bde8 🐞 [tmp] - nginx.conf - disable .js file caching
Some checks reported errors
continuous-integration/drone/push Build was killed
2021-01-11 21:10:40 +01:00
8c4b595c30 Merge branch 'dev' into feature/12-user-management 2021-01-11 21:08:44 +01:00
be629e5c6b 🕕 set manual refresh time to 2min
Some checks reported errors
continuous-integration/drone/push Build was killed
2021-01-11 21:08:39 +01:00
63569684a3 ℹ update jwtinfo store on token refresh 2021-01-11 21:08:14 +01:00
5937a0d7ce 🔒 added rendering based on permission level
ref #12
2021-01-11 21:07:30 +01:00
4512272c1c UserDetail - delete
ref #12
2021-01-11 21:06:47 +01:00
ce1f3842e0 🖊 UserDetail - reactivity on edit + update functionality
ref #12
2021-01-11 20:41:57 +01:00
ee01c3a059 🚀RELEASE v0.1.6
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-10 18:19:45 +01:00
81c1537bad 🔒 UserDetail - added basic layout for permission change
ref #12
2021-01-10 18:14:11 +01:00
98ecfab032 UserDetail multiselect layout for groups
ref #12
2021-01-10 18:02:36 +01:00
b948b8c1a4 UserDetail - placeholder for permission picker 🔒
ref #12
2021-01-10 17:55:13 +01:00
f856c6ae37 📧 UserDetail - email input
ref #12
2021-01-10 17:54:50 +01:00
2dd2580530 Merge branch 'dev' into feature/12-user-management 2021-01-10 17:34:12 +01:00
330755c63e 🚀RELEASE v0.1.5
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-10 17:18:55 +01:00
9cf0174b41 Merge commit '16f572480ad55425890061f9dad65fe85f2f39ad' into dev
close #30
2021-01-10 17:18:33 +01:00
16f572480a add versionbuilder script to release hook
ref #30
2021-01-10 17:18:16 +01:00
b8a9e4f272 📅 dynamic copyright year in Footer component
ref #30
2021-01-10 17:17:07 +01:00
c089bb3929 ⤵ load dynamic build info in Footer component
ref #30
2021-01-10 17:16:51 +01:00
3caa1fc277 🔨 sample build of index.html with versionbuilder script
ref #30
2021-01-10 17:16:07 +01:00
43b406592e 👀 improved Footer layout + display on Login component
ref #30
2021-01-10 17:15:46 +01:00
1dd6674faa added versionbuilder.js script
ref #30
2021-01-10 17:15:01 +01:00
4674b52717 🚀RELEASE v0.1.4
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-10 15:30:59 +01:00
cd5831251a Merge commit '45ec97066f425ac2ac66914be649cbd5a1038e10' into dev
close #20
2021-01-10 15:30:42 +01:00
45ec97066f 🌎 add remaining translation keys for filepond
ref #20
2021-01-10 15:30:13 +01:00
b08c0f145a add basic i18n logic to filepond
ref #20
2021-01-10 15:26:52 +01:00
f0c100aee4 UsersOverview - user delete
ref #12
2021-01-10 15:16:02 +01:00
0e31ba212f About - change license modal icon to "legal"
Some checks reported errors
continuous-integration/drone/push Build was killed
2021-01-10 15:04:05 +01:00
692c906cd2 🌎 About - i18n 2021-01-10 15:03:32 +01:00
4f3837ac45 new license file version [CI SKIP] 2021-01-10 13:47:30 +00:00
ccb5125a48 🚀RELEASE v0.1.3
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-10 14:46:31 +01:00
1c356a41f5 🌎 improved i18n for AddUserModal and UserDetail
ref #12
2021-01-10 14:45:34 +01:00
6012d0577e 🖊 add basic UserDetail editing reactivity
ref #12
2021-01-10 14:26:52 +01:00
5ec1dfa8b0 sample layout for advanced search
ref #12
2021-01-10 13:35:46 +01:00
3754f09b2f 🌎 added i18n for UsersOverview
ref #12
2021-01-10 13:31:46 +01:00
b9e0be4483 🔍 UsersOverview table - basic fuzzy search
ref #12
2021-01-10 13:30:26 +01:00
e1d5d54cfb 🧹 simplified UsersOverview table
ref #12
2021-01-10 13:30:04 +01:00
e97c5c3b7e Merge branch 'dev' into feature/12-user-management 2021-01-10 13:09:42 +01:00
242b5afbe9 🚀RELEASE v0.1.2
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-10 13:08:24 +01:00
12e5835360 Merge branch 'dev' of https://git.odit.services/lfk/frontend into dev 2021-01-10 13:07:50 +01:00
4af3cd158d Merge commit '2a37dfafa426e070aa136d171a1a01aa7f609d18' into dev
close #29
2021-01-10 13:07:43 +01:00
2a37dfafa4 dropped redundant console.log
ref #29
2021-01-10 13:06:48 +01:00
2cbb431acc 💾 save new auth data to localstorage
ref#29
2021-01-10 13:06:01 +01:00
d92c6c0de9 🔒 added basic manual refresh every 4mins
ref #29
2021-01-10 13:03:48 +01:00
19887c8f96 Merge branch 'dev' into feature/12-user-management 2021-01-10 12:37:18 +01:00
519ba79e1d new license file version [CI SKIP] 2021-01-10 11:36:40 +00:00
4ccd18ca9f Merge commit '5810b4ec4396ad650d90493fb48e2a8320865b42' into dev
All checks were successful
continuous-integration/drone/push Build is passing
close #4
2021-01-10 12:35:44 +01:00
5810b4ec43 🚀RELEASE v0.1.2-1 2021-01-10 12:30:23 +01:00
52aa99681b 🧪 modified auto-changelog to commit CHANGELOG.md
ref #4
2021-01-10 12:30:11 +01:00
0276c3deeb 🚀RELEASE v0.1.2-0 2021-01-10 12:26:07 +01:00
b28f76a1d4 🧪 experimental - auto changelog generation
ref #4
2021-01-10 12:25:42 +01:00
a66f6bbec8 🚀RELEASE v0.1.1 2021-01-10 12:24:29 +01:00
139b3294cd 🧪 move changelog generation to default 2021-01-10 12:24:19 +01:00
1aac783df3 🚀RELEASE v0.1.0 2021-01-10 12:21:35 +01:00
e361c89f6c add basic package script
ref #4
2021-01-10 12:18:22 +01:00
52a19c2036 added basic release-it config
ref #4
2021-01-10 12:18:00 +01:00
0023e22524 added release-it dev dependency
ref #4
2021-01-10 12:17:49 +01:00
b9aa00e8de Merge branch 'dev' into feature/12-user-management 2021-01-10 11:42:35 +01:00
8f25a874cb move serviceworker registration to separate module
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-10 11:42:30 +01:00
ded31980bf userdetail - dynamic buttons
ref #12
2021-01-09 19:11:46 +01:00
264e0d7ed9 AddUserModal - data validation
ref #12
2021-01-09 18:55:54 +01:00
7cf2ffce2d 🐞 fix malfuntion in logout logic
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-09 18:39:13 +01:00
848fb2fb65 Merge branch 'feature/12-user-management' into dev
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-09 18:29:47 +01:00
6529907a13 display full user names in overview table
ref #12
2021-01-09 18:27:22 +01:00
e5ec98bf6f basic AddUserModal ui
ref #12
2021-01-09 18:26:14 +01:00
2048533fda actually perform user logout (recreate Cookies, invalidate token) 2021-01-09 18:24:57 +01:00
cb58fdfd8e working on AddUserModal
ref #12
2021-01-09 17:39:17 +01:00
9684c22da3 simplified css classes for cleanliness 2021-01-09 17:15:52 +01:00
59d5ad5bd3 logic cleanup 🧠
All checks were successful
continuous-integration/drone/push Build is passing
ref #12
2021-01-09 16:06:03 +01:00
71c761187f UserOverview cleanup
ref #12
2021-01-09 15:58:58 +01:00
11457b2792 added Breadcrumb nav to UserDetail
ref #12
2021-01-09 15:40:10 +01:00
4fd1ac28c5 display profilepic properly
ref #12
2021-01-09 15:39:56 +01:00
8cf73a2be0 added middlename input field to mock
ref #12
2021-01-09 15:39:43 +01:00
644b9a7683 proper middlename display support
ref #12
2021-01-09 15:39:28 +01:00
23e03bec35 UserDetailOne sample ui
All checks were successful
continuous-integration/drone/push Build is passing
ref #12
2021-01-09 14:16:07 +01:00
9e19c48258 added general PromiseError component
ref #12
2021-01-09 14:15:55 +01:00
e7f63cf07b Merge branch 'dev' into feature/12-user-management 2021-01-09 14:05:00 +01:00
c38d33a549 ignore licenses.json in serviceworker
All checks were successful
continuous-integration/drone/push Build is passing
ref #17
2021-01-09 14:02:10 +01:00
1c2636d669 i18n 🌍
ref #17
2021-01-09 14:01:20 +01:00
c0328c5cdb AboutPage - read license text modal
ref #17
2021-01-09 13:57:25 +01:00
919910d4a8 bump to @odit/license-exporter@0.0.9 for version support
ref #17
2021-01-09 13:56:55 +01:00
9ab72aed19 license.json export + usage
ref #17
2021-01-09 13:32:54 +01:00
0e08c7f075 working on user detail page
ref #12
2021-01-09 12:54:55 +01:00
d5703365e4 first UserDetail page mockup
ref #12
2021-01-09 12:46:47 +01:00
58d68c8324 User Overview table expansion
ref #12
2021-01-09 12:46:35 +01:00
3c4a10944e added routing to UserDetail page
ref #12
2021-01-09 12:46:21 +01:00
6e4fe37378 general dependency bump 2021-01-09 12:07:29 +01:00
04794ff75e Merge branch 'feature/12-user-management' into dev
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-08 21:20:44 +01:00
f7d7b837c4 general dependency bump 2021-01-08 21:20:20 +01:00
08278b36a5 basic User Components
ref #12
2021-01-08 21:19:43 +01:00
9aeb99d775 dependency bump
ref #12
2021-01-08 21:19:05 +01:00
e8c98a0a29 basic ForgotPassword improvements
All checks were successful
continuous-integration/drone/push Build is passing
validation, steps towards error handlind
2021-01-08 17:32:31 +01:00
b107f5de95 move OpenAPI config to App component rather than Login 2021-01-08 17:31:37 +01:00
0c7bc07d67 🐞👀 visual fix in Footer component 2021-01-08 16:28:38 +01:00
970a7c58d3 Merge commit '2657f30cf3acaa592408d2d4cddcb02bf76bb6af' into dev
 close #27
2021-01-08 16:24:11 +01:00
2657f30cf3 use outsideclick custom directive in AddTrackModal component
ref #27
2021-01-08 16:23:45 +01:00
a042c8a870 added outsideclick as custom directive
ref #27
2021-01-08 16:23:20 +01:00
fc5c8d1309 Login Component Accessibility 👀
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-07 20:56:24 +01:00
32e4f223f8 NGINX config - fix 404 error for SPA usage 2021-01-07 20:56:09 +01:00
1f5057438f NGINX config - enable brotli compression 2021-01-07 20:55:49 +01:00
dbc660c48e remove duplicate class usage in Dash Component
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-07 20:32:56 +01:00
be440684b3 Merge commit '80d30a8e5425f4041e79c299095c36386b8d7777' into dev
All checks were successful
continuous-integration/drone/push Build is passing
close #26
2021-01-07 20:24:44 +01:00
80d30a8e54 ▶ ENTER key submit
ref #26
2021-01-07 20:23:45 +01:00
0c3e74f8a3 escape key support
ref #26
2021-01-07 20:21:17 +01:00
ac952b6906 📦 further Dockerfile/ Bundle optimizations
All checks were successful
continuous-integration/drone/push Build is passing
close #25
2021-01-07 20:14:45 +01:00
43ecd83213 general + PWA optimizations
All checks were successful
continuous-integration/drone/push Build is passing
selfhosting logo + PWA score optimization
2021-01-07 19:55:18 +01:00
aaef97dd43 🐳 added Docker buildsteps for sw generation
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-07 18:27:44 +01:00
bc66ebbf0a PWA optimizations
All checks were successful
continuous-integration/drone/push Build is passing
this supports asset caching + proper handling of the /env.js file (might have lead to config errors in previous versions)
2021-01-07 18:16:36 +01:00
44029c812e Dashboard - fix accessibility focus state on nav Dropdown + Logout
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-07 14:18:22 +01:00
3e8d0b5462 🐞fixed snowpack config for bundle optimization 2021-01-07 14:17:45 +01:00
6915123973 re-enable Tailwind compile with postcss 2021-01-07 14:16:03 +01:00
089813146f Merge commit 'bb0eb6d1e276186af2c1e5d26abda4413c278981' into dev
All checks were successful
continuous-integration/drone/push Build is passing
close #11
2021-01-05 22:24:18 +01:00
bb0eb6d1e2 AddTrackModal - minlaptime validation
ref #11
2021-01-05 22:15:20 +01:00
388fc8f239 AddTrackModal - autofocus on modal open
ref #11
2021-01-05 22:11:06 +01:00
0e7640bf86 AddTrackModal - trap focus if active
ref #11
2021-01-05 22:07:38 +01:00
d45eec94ab AddTrackModal - placeholder input
ref #11
2021-01-05 22:05:22 +01:00
7bb5a18527 AddTrackModal - add track icon
ref #11
2021-01-05 22:05:06 +01:00
fe297f6779 AddTrackModal - more input validation response
ref #11
2021-01-05 22:02:41 +01:00
591dc09228 AddTrackModal - basic validation w/ class + layout responses
ref #11
2021-01-05 21:53:06 +01:00
7d1b51918f delete confirmation
ref #11
2021-01-05 21:42:42 +01:00
e5fcb2ef68 Track edit animation
ref #11
2021-01-05 21:38:57 +01:00
34ecb8b2fb move toastify css to app base
ref #11
2021-01-05 21:38:47 +01:00
68de076227 edit is working 🎉
ref #11
2021-01-05 21:30:01 +01:00
039fd8f90e basic edit logic in table
ref #11
2021-01-05 21:16:50 +01:00
02bfecd540 EmptyState fixup
ref #11
2021-01-05 20:34:10 +01:00
10bf88e4ba fixed datatable-emptystate transition
ref #11
2021-01-05 20:27:06 +01:00
6e00ac8f1f improved empty state
ref #11
2021-01-05 18:31:36 +01:00
35a9aa40cb basic track deletion working
ref #11
2021-01-05 18:31:26 +01:00
c23fb5a450 Merge branch 'feature/24-i18n-formatting' into dev
All checks were successful
continuous-integration/drone/push Build is passing
close #24
2021-01-04 20:29:58 +01:00
1cd03ef027 add package script
ref #24
2021-01-04 20:29:43 +01:00
17e7778d15 re-added missing i18n keys
All checks were successful
continuous-integration/drone/push Build is passing
ref #11
2021-01-04 19:59:18 +01:00
70e10f7a70 demo run
ref #24
2021-01-04 19:55:23 +01:00
947482c1b5 improved order script for scalability
ref #24
2021-01-04 19:55:12 +01:00
f9aa262cab basic formatting script
ref #24
2021-01-04 19:52:05 +01:00
ff91995337 Merge branch 'dev' into feature/11-tracks-management
All checks were successful
continuous-integration/drone/push Build is passing
# Conflicts:
#	src/locales/en.json
2021-01-04 19:40:53 +01:00
5b15141ecc general cleanups
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-04 19:39:57 +01:00
008c91a552 track lap time interactive placeholder
ref #11
2021-01-04 19:37:17 +01:00
d830727036 AddTrackModal padding style
ref #11
2021-01-04 19:37:02 +01:00
dadccc1b5f 🌍 i18n lap time
ref #11
2021-01-04 19:33:48 +01:00
6022953417 Merge branch 'dev' into feature/11-tracks-management 2021-01-04 19:20:45 +01:00
e62277bdd2 tinro dependency bump
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-04 19:20:41 +01:00
7210f1b947 include minimum lap times
ref #11
2021-01-04 19:20:13 +01:00
8af63fc22a Merge commit '3a702aa91e768ab58e017d859732fcac960edac6' into feature/11-tracks-management 2021-01-04 18:24:56 +01:00
3a702aa91e 🎨 OrgDetail style fix
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-04 18:24:35 +01:00
312ddef193 Merge branch 'feature/17-license_collection' into dev
All checks were successful
continuous-integration/drone/push Build is passing
close #17
2021-01-03 19:39:27 +01:00
236aba89c2 bump lfk client 2021-01-03 19:34:03 +01:00
ba87349a88 dependency bump 2021-01-03 19:07:53 +01:00
9527167fbc StatCards direct linking to detail page 2021-01-03 19:01:14 +01:00
9309ea9a30 move FormLayout component to Settings Page 2021-01-03 19:00:59 +01:00
02d8888d97 🎨 general page styles 2021-01-03 18:54:54 +01:00
56556bad6e Merge branch 'dev' into feature/11-tracks-management
# Conflicts:
#	src/locales/en.json
2021-01-03 18:18:31 +01:00
84430854df 🌍 i18n 2021-01-03 18:14:40 +01:00
3f961dd3bd general page cleanups
let's go, devspeed 🚀
2021-01-03 18:14:31 +01:00
5c218c64ab improved sidebar icons 2021-01-03 18:04:03 +01:00
51c9c3fe3c new layout margin from page title
ref #11
2021-01-03 17:52:21 +01:00
281747a681 🌍 i18n
ref #11
2021-01-03 17:52:02 +01:00
f37ba1dbf7 reactivity on adding a new Track
ref #11
2021-01-03 17:51:46 +01:00
48ddc62192 🌍 translations
ref #11
2021-01-03 16:01:46 +01:00
1954909786 Merge branch 'dev' into feature/11-tracks-management 2021-01-03 15:55:50 +01:00
b1114634e8 Component + Login cleanup 2021-01-03 15:55:46 +01:00
501bf7a5f4 AddTrackModal action
ref #11
2021-01-03 15:55:29 +01:00
9975c0cf64 Tracks datatable action ui
ref #11
2021-01-03 15:55:16 +01:00
f5d0e285fb AddTrackModal cleanup
ref #11
2021-01-03 15:53:13 +01:00
89fcfe6a49 Dashboard component cleanup 2021-01-03 14:03:08 +01:00
72f1645767 Track page cleanup 2021-01-03 13:59:58 +01:00
9af4cfe73f Tracks page cleanup 2021-01-03 13:50:31 +01:00
9b036de4d7 Dashboard code cleanup 2021-01-03 13:45:38 +01:00
c2cf8a0989 Tracks sample page w/ modal
ref #23
2021-01-03 13:45:04 +01:00
728dd40f16 add Tracks route to app
ref #23
2021-01-03 13:44:45 +01:00
f3f318fd01 TracksOverview route 2021-01-03 13:44:22 +01:00
05306c75a5 Footer component cleanup 2021-01-03 12:22:15 +01:00
b27fb8c0b5 Dashboard - dropdown arrow change
ref #23
2021-01-03 00:58:57 +01:00
0e5fbb8835 general UI cleanup
ref #23
2021-01-03 00:58:39 +01:00
12eb207605 🔧 re-enable jwtinfo userdetails in Dashboard
ref #23
2021-01-02 23:04:58 +01:00
b9410dc5f1 small bugfix
ref #23
2021-01-02 22:32:26 +01:00
46491f38a0 ♻️ drop svelte-spa-router
ref #23
2021-01-02 22:25:52 +01:00
3396d17358 hash based routing in env file
ref #23
2021-01-02 22:23:32 +01:00
a50ea15b38 🚚 move to tinro svelte router
ref #23
2021-01-02 22:23:13 +01:00
1473267e8c [tmp] - move to cdn usage
for dev server compilation/ faster startup times
2021-01-02 22:16:54 +01:00
112516ccfd gitignore yarn.lock package-lock.json 2021-01-02 22:14:40 +01:00
1104ab20c7 dependency bump
postcss + gridjs
2021-01-02 22:14:07 +01:00
9fbe1c7c6c 🎨 TailwindStyles - Chromium fix 2021-01-02 22:12:33 +01:00
0842640fec Dependency: Bumped license-exporter version
ref odit/license-exporter#1 odit/license-exporter#3
2021-01-02 20:50:55 +01:00
b538b6cb71 Updated dev branch name 2021-01-01 14:02:29 +01:00
4c96b9a3e0 Initial license export
ref #18
2020-12-31 22:42:50 +01:00
9bf21a0eeb Added drone file with pipeline for dev
ref #18
2020-12-31 22:41:30 +01:00
1fba3ef9e4 Added script for license export
ref #18
2020-12-31 22:41:10 +01:00
6ed818ef2d Merge pull request 'feature/10-dashboard-stats' (#21) from feature/10-dashboard-stats into develop
Reviewed-on: #21closes #10
2020-12-31 16:58:16 +00:00
4f02a5bac0 🖼 new icons + i18n for card titles
ref #10
2020-12-31 16:33:08 +01:00
f1b2c1de26 🌍 i18n - general_promise_error
ref #10
2020-12-31 16:32:42 +01:00
3d93b04264 StatCards - fetch real data from API
ref #10
2020-12-31 16:19:58 +01:00
e9a6637eb9 move StatCards up in Dashboard UI
ref #10
2020-12-31 16:19:40 +01:00
b4df123c04 added translations 🌍
ref #10
2020-12-31 16:18:32 +01:00
725edfbb19 Merge pull request 'feature/8-login-preserve' (#19) from feature/8-login-preserve into develop
Reviewed-on: #19

close #8
2020-12-30 19:05:31 +00:00
813c058a9a 👋 sample user info usage on dashboard
ref #8
2020-12-30 19:56:21 +01:00
13ccb56354 💾 login state preserve (localstorage) + ℹ JWT payload parsing
ref #8
2020-12-30 19:55:57 +01:00
51c18e03a7 Merge pull request 'feature/5-base-components' (#18) from feature/5-base-components into develop
Reviewed-on: #18

close #5
2020-12-30 17:24:14 +00:00
517c6381ba 🐞 translation fix
ref #5
2020-12-30 18:23:19 +01:00
71b6258bc3 🧹 drop nanoid dependency
ref #5
2020-12-30 18:21:43 +01:00
45cd532c1a improved About page + route
ref #5
2020-12-30 18:16:42 +01:00
1dc498302f more translations 2020-12-30 17:55:43 +01:00
a977b34adb About page
ref #5
2020-12-30 17:55:35 +01:00
e48502fb41 move filepond import to css-in-js
ref #5
2020-12-30 17:26:53 +01:00
b2b05a4ec9 Dashboard - use sample components
ref #5
2020-12-30 17:25:23 +01:00
c65c138830 Dashboard - demo urls
ref #5
2020-12-30 12:45:01 +01:00
4c161b3c70 Dashboard - hide overflow
ref #5
2020-12-30 12:41:38 +01:00
7270ce9d32 new Dashboard
ref #5
2020-12-30 12:35:15 +01:00
428d2e2579 general cleanups
ref #5
2020-12-30 12:35:07 +01:00
7e69eeef29 Avatars
ref #5
2020-12-30 11:55:56 +01:00
2fa102b543 Badges
ref #5
2020-12-30 11:55:48 +01:00
d188ae658a NotFound
ref #5
2020-12-30 11:55:38 +01:00
e6815eb75f Tabs
ref #5
2020-12-30 11:55:30 +01:00
3bd954e9ff Tags 2020-12-30 11:55:16 +01:00
a379327d67 DataTable - load from json object
ref #5
2020-12-30 10:58:27 +01:00
773800e732 Sidebar - UX logic w/ interactivity
ref #5
2020-12-30 10:58:10 +01:00
e4c6c5711d 🚧 working on sidebar
ref #5
2020-12-30 10:54:08 +01:00
ada68887a2 filepond integration 2020-12-30 10:37:10 +01:00
3c36bea07c i18n compatible datatable 2020-12-29 21:32:06 +01:00
016f08b07c Login component - enter key listener 2020-12-29 21:31:55 +01:00
9633a09b73 bump client to 0.0.5 2020-12-29 21:31:38 +01:00
d2938591bf DataTable
ref #5
2020-12-23 20:13:08 +01:00
4765faeddc Sidebar
ref #5
2020-12-23 20:12:55 +01:00
bf7ea2d6ee Merge commit 'de7cd1267df684b5befd6111aa347e76c6f2ffdd' into develop
closes #7
2020-12-23 19:12:10 +01:00
de7cd1267d added webmanifest 🧾
ref #7
2020-12-23 19:11:23 +01:00
fe40fd9a91 add theme color to index
ref #7
2020-12-23 19:11:07 +01:00
13b6b32041 basic service worker registration via workbox
ref #7
2020-12-23 19:10:50 +01:00
9a53e32691 Bump snowpack to 3.0.0-rc.2 🧪 2020-12-22 20:19:14 +01:00
0dc8bffaec dependency bumps 2020-12-22 20:15:32 +01:00
6f787f63f7 updated Dockerfile for snowpack ❄
ref #3
2020-12-22 20:13:15 +01:00
040359aa93 cleaned up Login component for darkmode compatibility 2020-12-22 19:57:25 +01:00
2a155a5cd4 🌑 enable tailwind darkmode in config
close #6
2020-12-22 19:57:08 +01:00
663e41abec basic move to snowpack
close #3
2020-12-22 19:53:54 +01:00
7f340f2c7b tailwind config
close #2
2020-12-22 19:52:45 +01:00
113 changed files with 8826 additions and 2673 deletions

3
.dockerignore Normal file
View File

@ -0,0 +1,3 @@
public/env.sample.js
public/workbox-*.js
public/workbox-*.js.map

41
.drone.yml Normal file
View File

@ -0,0 +1,41 @@
---
kind: pipeline
type: docker
name: build:dev
steps:
- name: run full license export
depends_on: ["clone"]
image: node:alpine
commands:
- yarn
- yarn licenses:export
- name: push new licenses file to repo
depends_on: ["run full license export"]
image: appleboy/drone-git-push
settings:
branch: dev
commit: true
commit_message: new license file version [CI SKIP]
author_email: bot@odit.services
remote: git@git.odit.services:lfk/frontend.git
ssh_key:
from_secret: GITLAB_SSHKEY
- name: build dev
image: plugins/docker
depends_on: [clone]
settings:
username:
from_secret: DOCKER_REGISTRY_USER
password:
from_secret: DOCKER_REGISTRY_PASSWORD
repo: registry.odit.services/lfk/frontend
tags:
- dev
registry: registry.odit.services
trigger:
branch:
- dev
event:
- push

13
.gitignore vendored
View File

@ -1,6 +1,11 @@
.vscode
.idea
node_modules node_modules
dist build
dist-ssr package-lock.json
yarn.lock
*.map
public/env.js public/env.js
public/sw.js
public/index.html
public/workbox-*.js
svelte.config.js
public/index.html

13
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,13 @@
{
"recommendations": [
"2gua.rainbow-brackets",
"christian-kohler.npm-intellisense",
"remimarsal.prettier-now",
"svelte.svelte-vscode",
"lokalise.i18n-ally",
"fivethree.vscode-svelte-snippets"
],
"unwantedRecommendations": [
"antfu.i18n-ally"
]
}

4
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,4 @@
{
"i18n-ally.localesPaths": "src/locales",
"i18n-ally.keystyle": "nested"
}

521
CHANGELOG.md Normal file
View File

@ -0,0 +1,521 @@
### Changelog
All notable changes to this project will be documented in this file. Dates are displayed in UTC.
#### [0.6.0](https://git.odit.services/lfk/frontend/compare/0.5.0...0.6.0)
- Merge pull request 'feature/52-runner-filters' (#63) from feature/52-runner-filters into dev [`#52`](https://git.odit.services/lfk/frontend/issues/52)
- Merge pull request 'feature/43-password-reset' (#61) from feature/43-password-reset into dev [`#43`](https://git.odit.services/lfk/frontend/issues/43)
- Merge pull request 'feature/51-teamoverview-badge-org' (#59) from feature/51-teamoverview-badge-org into dev [`#51`](https://git.odit.services/lfk/frontend/issues/51)
- Merge pull request 'feature/47-sidebar-responsiveness' (#60) from feature/47-sidebar-responsiveness into dev [`#47`](https://git.odit.services/lfk/frontend/issues/47)
- Merge pull request 'feature/15-runner-import' (#58) from feature/15-runner-import into dev [`#15`](https://git.odit.services/lfk/frontend/issues/15)
- Merge pull request 'feature/56-footer-version-loading' (#57) from feature/56-footer-version-loading into dev [`#56`](https://git.odit.services/lfk/frontend/issues/56)
- Merge pull request 'feature/46-imprint-privacy' (#55) from feature/46-imprint-privacy into dev [`#46`](https://git.odit.services/lfk/frontend/issues/46)
- Merge pull request 'feature/44-require-mail-addresses' (#54) from feature/44-require-mail-addresses into dev [`#44`](https://git.odit.services/lfk/frontend/issues/44)
- Merge pull request 'feature/45-component-drop' (#53) from feature/45-component-drop into dev [`#45`](https://git.odit.services/lfk/frontend/issues/45)
- 🧹 drop old + unused components Dash + LoginAlt [`eeee272`](https://git.odit.services/lfk/frontend/commit/eeee272f0353cd543dca0efbdf23f5972a771f6c)
- ✨ added basic UI for ResetPassword [`b18a99e`](https://git.odit.services/lfk/frontend/commit/b18a99e4df6111e137a8081c3e6619da6f5af30c)
- 🧹 remove placeholder options from Dashboard sidebar [`16ac96c`](https://git.odit.services/lfk/frontend/commit/16ac96c64e86a6f97e179d70b586f20869ffe663)
- 👀 ResetPassword - success and error states [`8b2f196`](https://git.odit.services/lfk/frontend/commit/8b2f1965e2a754da6e40a3051e8ae3e771d70336)
- added Privacy page [`5741cbe`](https://git.odit.services/lfk/frontend/commit/5741cbe7562542ba2b81a9a6d6be7fb0f5145801)
- ImportRunnerModal - differenciate between team and org import [`acf0562`](https://git.odit.services/lfk/frontend/commit/acf0562851a77b9122473ffb1753a94b4272e53b)
- ✨ RunnersOverview - basic working filter [`575b4ce`](https://git.odit.services/lfk/frontend/commit/575b4ce9708625fbec23c49101f44825c6a75bce)
- basic select filtering in RunnersOverview [`e415258`](https://git.odit.services/lfk/frontend/commit/e415258787c776d4a5291632f47c2fcceba9a040)
- WIP on filter [`e23821a`](https://git.odit.services/lfk/frontend/commit/e23821a7cbe73fda420e4bcaaa2dbf5a89b56cc9)
- ✨ layout for Import from RunnerOverview [`c698508`](https://git.odit.services/lfk/frontend/commit/c6985087a8b51d6e1d8fb71b1eb8a659b8ed34e6)
- ✨ working Imprint page [`6401aeb`](https://git.odit.services/lfk/frontend/commit/6401aeb3a850b1dd9ec4eca5c181ebcf3ed8b7e9)
- 🌍 i18n for ResetPassword [`428a8a1`](https://git.odit.services/lfk/frontend/commit/428a8a10ffa96ae2f04c718d678439d36e6cb857)
- ⏫ dependency bump [`6614242`](https://git.odit.services/lfk/frontend/commit/6614242df6a9679f60bc3996c0317d0870a81ac4)
- added Imprint route /imprint [`bbec9ff`](https://git.odit.services/lfk/frontend/commit/bbec9ffcdf0be2cf37501a355c5678ed9cd46a1d)
- ⏫ general dependency bump [`369b099`](https://git.odit.services/lfk/frontend/commit/369b09972c786e5381d7bae667def6a15e21a45b)
- AddUserModal - enforce email input [`7131ba9`](https://git.odit.services/lfk/frontend/commit/7131ba99b6917dc0fb99f93ab40495b9702024ab)
- Org badge in TeamsOverview [`3490abe`](https://git.odit.services/lfk/frontend/commit/3490abe9a70ca3bd1725ae005c4031f25996f2a7)
- RunnersOverview - support should_display_based_on_id search [`5dc11c2`](https://git.odit.services/lfk/frontend/commit/5dc11c285d4e2155a3945bc44cbfcf61fe02c5f3)
- RunnersOverview badge to team/org [`a02be78`](https://git.odit.services/lfk/frontend/commit/a02be78df52f944d5842f8d0e9bbf62ba95e9ef0)
- working import binding [`7dd0881`](https://git.odit.services/lfk/frontend/commit/7dd0881d293a7c46133cccd947e668178058ddd8)
- Footer linking [`945963d`](https://git.odit.services/lfk/frontend/commit/945963db326e937456103f0092bbc684b0d0ec45)
- UserDetail - invalid email UI feedback [`7897820`](https://git.odit.services/lfk/frontend/commit/7897820632c7b9e1e00fbf3f86a1eb7e01794fa5)
- reactivity in import table [`b501342`](https://git.odit.services/lfk/frontend/commit/b5013426e6d4b92f56a8b7a42f4f13411c2cf61c)
- 😬 use actual team id for RunnersOverview badge [`1fac0c8`](https://git.odit.services/lfk/frontend/commit/1fac0c8640460278bf6e2460e685e192e8a984f3)
- 🐞 re-add sidebar component [`f8014c6`](https://git.odit.services/lfk/frontend/commit/f8014c62132eaafaa5edb486f3839b9502f3287f)
- ⚡ load version on DOMContentLoaded [`901c7c1`](https://git.odit.services/lfk/frontend/commit/901c7c12418f09a29235afe8df5982f964fc61db)
- UserDetail - enforce email input [`0dd9de2`](https://git.odit.services/lfk/frontend/commit/0dd9de2abc68ce10c4c763382945b92c0cd4e404)
- use onMount event instead of DOMready [`c2d7a31`](https://git.odit.services/lfk/frontend/commit/c2d7a319a0761a77597fe365083b12c4c045a7ba)
- new license file version [CI SKIP] [`43df188`](https://git.odit.services/lfk/frontend/commit/43df188df149401c966b8d7bb6e28fbbe39a83f1)
- new license file version [CI SKIP] [`b25dc62`](https://git.odit.services/lfk/frontend/commit/b25dc623cf12aba88576cd8784a20b0fefc5466e)
- ⏮decode base64 reset key [`92fee08`](https://git.odit.services/lfk/frontend/commit/92fee08dc40cf04fa1dc6d6e4aaf19f0b752031f)
- 🧹 formatting [`7b7e484`](https://git.odit.services/lfk/frontend/commit/7b7e4840918b21951fa9e267044d623e9de7b0f5)
- Merge commit '65f49489ad2e0cff30560e4c326ca7294d7f6190' into feature/51-teamoverview-badge-org [`f0be73c`](https://git.odit.services/lfk/frontend/commit/f0be73c2cd6c930273515a8773a2affd4068c92c)
- 👀 only display navbar on sm devices / hide on md and up [`42bd632`](https://git.odit.services/lfk/frontend/commit/42bd63253916ab339a62f64762802750dc315b4c)
- new license file version [CI SKIP] [`65f4948`](https://git.odit.services/lfk/frontend/commit/65f49489ad2e0cff30560e4c326ca7294d7f6190)
- new license file version [CI SKIP] [`e4e2bda`](https://git.odit.services/lfk/frontend/commit/e4e2bdac724f40584bf1526fa59b0f380ba8cba0)
- ⏫ client library bump [`80c3a90`](https://git.odit.services/lfk/frontend/commit/80c3a90d6fd14f1946b1e4ecd5e19314ca952c2d)
- new license file version [CI SKIP] [`0e04298`](https://git.odit.services/lfk/frontend/commit/0e04298b7c53e2dfd447f51b75c7c32f0b730eea)
- text selection color [`4e82369`](https://git.odit.services/lfk/frontend/commit/4e82369b160da3eaa5a32cf104aacc6250e7e31e)
- added basic styling to Imprint component [`6a0c129`](https://git.odit.services/lfk/frontend/commit/6a0c129d39f5e659411c2358f30d425720229f16)
- 🧹 remove ComponentDump from MainDashContent [`1bb79b1`](https://git.odit.services/lfk/frontend/commit/1bb79b1c98d166523b638a06ea9a354434ec1e08)
#### [0.5.0](https://git.odit.services/lfk/frontend/compare/0.4.0...0.5.0)
> 26 January 2021
- Merge pull request 'feature/12-user-management' (#39) from feature/12-user-management into dev [`#12`](https://git.odit.services/lfk/frontend/issues/12)
- Merge pull request 'feature/13-runner-management' (#42) from feature/13-runner-management into dev [`#13`](https://git.odit.services/lfk/frontend/issues/13)
- Merge pull request 'added permissions to dashboard sidebar' (#41) from feature/40-dynamic-sidebar-options into dev [`#41`](https://git.odit.services/lfk/frontend/issues/41)
- basic UserPermissions view [`9c6dc5b`](https://git.odit.services/lfk/frontend/commit/9c6dc5b4247e76be63a92294556be1e4a9154256)
- ✨ basic RunnerDetail [`03aa670`](https://git.odit.services/lfk/frontend/commit/03aa67034d5fc4ec64d5646a6583666301bb538e)
- 👀 ImportRunnerModal - layout cleanup [`2cc9b3e`](https://git.odit.services/lfk/frontend/commit/2cc9b3e1ede47bb1a4b598afc260d0bc06ed0902)
- ✨ basic ImportRunnerModal with CSV input [`b1e9f95`](https://git.odit.services/lfk/frontend/commit/b1e9f955b054cd17cb83f8364b6bc2b22159142f)
- ✨ basic xlsx + csv parsing [`74d9b94`](https://git.odit.services/lfk/frontend/commit/74d9b94119b49a77ff3e787a84f76c1e2e13d9e4)
- 🧹 darkmode classes cleanup [`259a76f`](https://git.odit.services/lfk/frontend/commit/259a76f46b9ac01b177100dfce0db367b517b3a0)
- UserDetail - basic permission badges [`7d4e939`](https://git.odit.services/lfk/frontend/commit/7d4e93912c4e1fd8a875f42a0f11b906f1ca0091)
- added permissions to dashboard sidebar [`acb86ae`](https://git.odit.services/lfk/frontend/commit/acb86ae2666ec3c336973184ec5cb1e2ffba9ebe)
- UserPermissions - working edit [`10d7955`](https://git.odit.services/lfk/frontend/commit/10d7955f99b1a640eb1ac83764c114a25cfcc07b)
- 🐞 ImportRunnerModal - table overflow fix [`dc1644e`](https://git.odit.services/lfk/frontend/commit/dc1644ed25b82afe6a51fbe0e1de69ddf54ef81d)
- 🧹 general runner cleanup [`9240e0c`](https://git.odit.services/lfk/frontend/commit/9240e0c90380fc6b44a3dde480bd2d29476b2a0f)
- 🚀RELEASE v0.5.0 [`2563e9d`](https://git.odit.services/lfk/frontend/commit/2563e9d1d6fe55544b04de8edd8c24cc1d3c9735)
- ✨ formatting [`6e5a4bc`](https://git.odit.services/lfk/frontend/commit/6e5a4bcb39259fc376262c16ad95555fd9cf7a0b)
- ✨ ImportRunnerModal usage in TeamDetail [`de0bd5f`](https://git.odit.services/lfk/frontend/commit/de0bd5fd57edba75faaa2ba097a2c7fb2f621f9a)
- 💣 process breaking changes for lib v0.3.0 [`dadb806`](https://git.odit.services/lfk/frontend/commit/dadb80608a55980f7b647d41c91061055287a250)
- 👀 basic ui interaction for ImportRunnerModal [`c382f77`](https://git.odit.services/lfk/frontend/commit/c382f770dc05f57a471389d3d4d012fd8439c014)
- 🐞 [bugfix] RunnerDetail update [`ec4bcd0`](https://git.odit.services/lfk/frontend/commit/ec4bcd093b6c94f2e3597de6de98db5ca47c5876)
- 🏃‍♂️ support for runner group edit [`8d92a75`](https://git.odit.services/lfk/frontend/commit/8d92a75ef03854397a7c01ed9ea22967b994f9f2)
- AddRunnerModal - improved team select [`1694c71`](https://git.odit.services/lfk/frontend/commit/1694c71528f9114622daae1bd16aaed9f94fc4ee)
- 🙋‍♂️🔒 UserDetail - permission layout ui [`65b36f8`](https://git.odit.services/lfk/frontend/commit/65b36f8e695479809566213ebcb64d51a7c6a52e)
- 🌎 added locale based csv/xlsx header parsing [`b606037`](https://git.odit.services/lfk/frontend/commit/b606037890856c1115315bb61d8469ff66dc4289)
- AddUserModal - username/email validation [`ff14f02`](https://git.odit.services/lfk/frontend/commit/ff14f024afda77e6f5f9e95a8f34e50cc8509790)
- UserDetail - link to permission page [`aaab95d`](https://git.odit.services/lfk/frontend/commit/aaab95d41400629683d89ee16013b6b4328c582c)
- UserDetail - add permission layout [`d65b463`](https://git.odit.services/lfk/frontend/commit/d65b463547a5cb4fb6f6de0f95795a66ebe92e31)
- RunnerDetail - button text fixes [`5552055`](https://git.odit.services/lfk/frontend/commit/5552055b989313e8999e424b6ad26c4398e8c6fa)
- 🧹 RunnerDetail cleanup + i18n 🌎 [`a871651`](https://git.odit.services/lfk/frontend/commit/a87165148a3ccdb4c4e10f2c6545d5fea9e78b30)
- ↙ added default fallback parsing to ImportRunnerModal [`ecad1ea`](https://git.odit.services/lfk/frontend/commit/ecad1ea839dbcea396217cb18fe335694406cdb1)
- 🌎 i18n [`366804a`](https://git.odit.services/lfk/frontend/commit/366804aa29eb0231e40017d1e5c3419486070174)
- ✨ ImportRunnerModal - compatibility for multi-component access [`f8a5913`](https://git.odit.services/lfk/frontend/commit/f8a59133a2534efdc9e13104eb64157a9724e746)
- ✨ use ImportRunnerModal in OrgDetail + Orgs [`8aa1d94`](https://git.odit.services/lfk/frontend/commit/8aa1d94a1aa976cc6cb4f7d3cc798822861676ad)
- ✨ ImportRunnerModal - hide team when loading from TeamDetail [`819b02a`](https://git.odit.services/lfk/frontend/commit/819b02a204cd43baaaf85d82cf0209a38028e6ce)
- 🚀 ImportRunnerModal - working demo [`6b91b22`](https://git.odit.services/lfk/frontend/commit/6b91b227136580180d5300b2614a6148995c4640)
- 🌎 i18n [`ae8bc01`](https://git.odit.services/lfk/frontend/commit/ae8bc01d9bab994aad6407102f3a6ef0167d008b)
- 🐞 fixed merge conflict errors [`cbc1d53`](https://git.odit.services/lfk/frontend/commit/cbc1d53cc257cbf60172dd4356831e43671ec626)
- ✨ basic cancel event dispatch from ImportRunnerModal [`cde4ec1`](https://git.odit.services/lfk/frontend/commit/cde4ec13efef8423464ebe90a4f0212a3be6cf77)
- 🧹 AddUserModal cleanup [`44f07ca`](https://git.odit.services/lfk/frontend/commit/44f07ca231307ebdbe38b822408c3965077053ef)
- 🙋‍♂️ UserDetail - active/inactive user state edit [`86c54e0`](https://git.odit.services/lfk/frontend/commit/86c54e04a84917383d3726b324c416ca1bd74627)
- UserDetail - update permission badges on change save [`358865d`](https://git.odit.services/lfk/frontend/commit/358865dc6a8e5a084078696048df99a67564a9e1)
- i18n 🌍 [`415f340`](https://git.odit.services/lfk/frontend/commit/415f340a68078e86fcf28d24b069ca030e28fc82)
- 👀 UsersOverview - disable advanced search [`b34e3ae`](https://git.odit.services/lfk/frontend/commit/b34e3aeed0f026308288fb568542cc0ba587b576)
- 🙋‍♂️ UserDetail - disable profile picture edit [`ef9fc59`](https://git.odit.services/lfk/frontend/commit/ef9fc596f57d63c1bf9c26614d746bf37394f832)
- 🌎 i18n [`2d48691`](https://git.odit.services/lfk/frontend/commit/2d4869128d2af5e7fa5e4bcf1d6b687fca6761e9)
- 🌎 i18n in ImportRunnerModal headers [`e34c91b`](https://git.odit.services/lfk/frontend/commit/e34c91b2cc64221cd553e80974a14448e3afc340)
- 🌎 last i18n keys [`6c2d13b`](https://git.odit.services/lfk/frontend/commit/6c2d13bd1721918f1ef847563cfad61284c40487)
- Fixed User group update [`1a799dc`](https://git.odit.services/lfk/frontend/commit/1a799dc30a4f051305e5279bb3325e64ab08f323)
- 🌎 UserDetail - more i18n keys [`e750c37`](https://git.odit.services/lfk/frontend/commit/e750c374739104c6aaac1abace9e9632b40342da)
- ⏫ general dependency bump [`b30b673`](https://git.odit.services/lfk/frontend/commit/b30b6734a1f4cc384f06147dfc657f2a1a05b3c0)
- 💻 updated VSCode recommended extensions [`fc21427`](https://git.odit.services/lfk/frontend/commit/fc214276859189c6e9b6fdc91eb440cf4afda7c0)
- 🐞 improved version builder from template [`09fd73b`](https://git.odit.services/lfk/frontend/commit/09fd73b13086025a31b4c1363331230e1c91e593)
- ⏫ general dependency bump [`9a1f7a1`](https://git.odit.services/lfk/frontend/commit/9a1f7a13f8669b690fb3f9533df7fdfa2f1bc093)
- 🌎 added more i18n keys [`169ffc1`](https://git.odit.services/lfk/frontend/commit/169ffc1b0b76c74350060c97218643789e0ea4f7)
- 😦 added missing dependencies [`822759a`](https://git.odit.services/lfk/frontend/commit/822759a6884eab81f62d96bd7e6f038fa8bc1926)
- fixed runner permissions [`e0356fa`](https://git.odit.services/lfk/frontend/commit/e0356fa360b74f91b38dc09351930c8c0778f845)
- Added friles to ignore [`9fbc1ba`](https://git.odit.services/lfk/frontend/commit/9fbc1ba0315fc0a2b7f3d95339cb17fc74e57903)
- ✨ new UsersEmptyState [`959b32d`](https://git.odit.services/lfk/frontend/commit/959b32de1c6fc74bec585630b3025babc1a8edc4)
- 🐞 fix package release for index template compatibility [`24bec66`](https://git.odit.services/lfk/frontend/commit/24bec66390bfddd57c0cd1d35de94a79bdbf477c)
- new license file version [CI SKIP] [`3d30734`](https://git.odit.services/lfk/frontend/commit/3d30734dc2716a88d823fe7a7650f17d14a8f18d)
- new license file version [CI SKIP] [`3281239`](https://git.odit.services/lfk/frontend/commit/3281239ff5b4968fb1dc8547e7c16f0789ec2b8c)
- 🐞 alphabetically sort permission targets in UserDetail [`ffd88ff`](https://git.odit.services/lfk/frontend/commit/ffd88ffc666d9d3add43dfa4ec4b6e01ca7084b5)
- new license file version [CI SKIP] [`e91e197`](https://git.odit.services/lfk/frontend/commit/e91e197873e524b93b2cf79286c66c8b776fbae2)
- Deleted files to ignore [`6704c07`](https://git.odit.services/lfk/frontend/commit/6704c07db0a00b17327a4b693b3f74ed41a1a41d)
- new license file version [CI SKIP] [`c6504c2`](https://git.odit.services/lfk/frontend/commit/c6504c2eaf1d0c5fb14a4035ccbf347d851068d9)
#### [0.4.0](https://git.odit.services/lfk/frontend/compare/0.3.1...0.4.0)
> 17 January 2021
- Merge commit 'a284806d3cb769030a4e28d0403190b746f8fc61' into dev [`#37`](https://git.odit.services/lfk/frontend/issues/37)
- ✨ AddRunnerModal [`66ffd8e`](https://git.odit.services/lfk/frontend/commit/66ffd8e936010960766e7f9021319d549e1d3e6b)
- ✨ RunnersOverview [`66a07c6`](https://git.odit.services/lfk/frontend/commit/66a07c6a51674a92d5e8459f250de2ab5ff6d902)
- ⚡ improved dev scripts for speed starts [`383f828`](https://git.odit.services/lfk/frontend/commit/383f82807f4090bdd2c3dcdc695b75093b854031)
- 🚀RELEASE v0.4.0 [`7d104a1`](https://git.odit.services/lfk/frontend/commit/7d104a151422a7c8306ef4d9f0ab25d26f8eb78b)
- ✨ dynamic contact info in AddRunnerModal [`d4579a9`](https://git.odit.services/lfk/frontend/commit/d4579a9a410a27676e0ed0285bd124696153aae4)
- 🐞 fix cross-env logic for faster dev starts ⚡ [`2ce4199`](https://git.odit.services/lfk/frontend/commit/2ce41990bf9911db11eb556ce4c9aa3b3e5ca16c)
- 👩‍💻 developer configs/ recommendations for VSCode [`5e02502`](https://git.odit.services/lfk/frontend/commit/5e02502a5c2b8ebf798cbc21856b4425f8510041)
- ⚡ re-enable PWA functionality via serviceworker [`a284806`](https://git.odit.services/lfk/frontend/commit/a284806d3cb769030a4e28d0403190b746f8fc61)
- 🔨 cleaned up build process + Dockerfile [`7e10c1d`](https://git.odit.services/lfk/frontend/commit/7e10c1db659c21cd737b5d1e10bf3e61c4e0de94)
- 🔨 cleaned up build process + Dockerfile [`1179063`](https://git.odit.services/lfk/frontend/commit/11790638d68e8c43f91448bd0c35f910a3d9e446)
- ⚡ improved serviceworker + PWA logic [`0583cbe`](https://git.odit.services/lfk/frontend/commit/0583cbe2664f8832c5eaa7fb155b3e6deccb2ed3)
- apply new gitignore config [`2e6874c`](https://git.odit.services/lfk/frontend/commit/2e6874c822f2f8e9a8a7b74b4765631ba08f0255)
- ⚡ PWA optimizations [`dccf7c6`](https://git.odit.services/lfk/frontend/commit/dccf7c6c8de0da0f25ad77e20822fb4fbf68a61a)
- ⏫ general dependency bumps [`03125b3`](https://git.odit.services/lfk/frontend/commit/03125b3a2d0ad4b12cffdec98f27da14f3f45f77)
- 🐞 gitignore fix [`e49dca0`](https://git.odit.services/lfk/frontend/commit/e49dca02754aa57cbe464066f11505db4a9b5ca9)
- gitignore fix [`a523379`](https://git.odit.services/lfk/frontend/commit/a523379b3a5f7f3cffecca82c0c066167da046ca)
- fix package:dev script [`aa6348a`](https://git.odit.services/lfk/frontend/commit/aa6348a29a6e9ecb9789681b7195527c4eef19e4)
- new license file version [CI SKIP] [`382cc3d`](https://git.odit.services/lfk/frontend/commit/382cc3d844bf0af7c46492907f8a9a78fadc25d0)
- 🧹 gitignore changes in public/index.html & svelte.config.js [`dd74d9e`](https://git.odit.services/lfk/frontend/commit/dd74d9ee89b80c46ce3d3347a3c7cbe34373019c)
#### [0.3.1](https://git.odit.services/lfk/frontend/compare/0.3.0...0.3.1)
> 16 January 2021
- Merge pull request 'feature/16-org-management' (#35) from feature/16-org-management into dev [`#16`](https://git.odit.services/lfk/frontend/issues/16)
- 🏃‍♂️🏃‍♂️🏃‍♂️ basic UI components for team management [`d87b879`](https://git.odit.services/lfk/frontend/commit/d87b879cc3d6c771a8a9932409e39068e1b2acdb)
- ✨ TeamDetail with edit,delete [`ccf09f9`](https://git.odit.services/lfk/frontend/commit/ccf09f97d5fb476113f24a9559a48bccd75fd0a5)
- 🔒 ConfirmTeamDeletion in TeamsOverview [`cbcce33`](https://git.odit.services/lfk/frontend/commit/cbcce336d68b0752daeaf4b5608c43ff6fa11c0d)
- 🔒 ConfirmOrgDeletion in OrgDetail [`d890112`](https://git.odit.services/lfk/frontend/commit/d8901126d0cc91cabe3b94a30a83f36e6288126d)
- ✨ basic TeamsOverview [`597e9e1`](https://git.odit.services/lfk/frontend/commit/597e9e1ea9da7c73bdcb8ef1ae1a13dfa68ff5a3)
- ✨ UX - ConfirmOrgDeletion cancel event reflection in datatable [`84a9cf0`](https://git.odit.services/lfk/frontend/commit/84a9cf069a4aa0940eaacc87ea67e745deabe939)
- 🐞 fix Dashboard sidebar responsiveness [`6a81e36`](https://git.odit.services/lfk/frontend/commit/6a81e369fa20f0bb2846365a45f96e91e95fe2e7)
- 🧹 Dashboard - drop header bar [`f1833f1`](https://git.odit.services/lfk/frontend/commit/f1833f13d57595c23abf29bce1a2795cbb05a116)
- ✨ AddTeamModal working [`9bb027e`](https://git.odit.services/lfk/frontend/commit/9bb027ec4c73483907d396180f739dc3a11b2404)
- 🧹 TeamDetail cleanup [`7654b79`](https://git.odit.services/lfk/frontend/commit/7654b795c756ca198bad77068823032714408535)
- 🚀RELEASE v0.3.1 [`64ade90`](https://git.odit.services/lfk/frontend/commit/64ade901ded75fa738c713446343a209eca89ce6)
- 🤝 attribution/ credits for icons and illustrations [`eb0dd3f`](https://git.odit.services/lfk/frontend/commit/eb0dd3f781f739c6511588a8e153c14a39096025)
- 🔒 re-enable confirmation in OrgOverview [`ce6002a`](https://git.odit.services/lfk/frontend/commit/ce6002a631dd3c140f3892c750d052e89c135653)
- ✨ added new empty states [`66e6cd8`](https://git.odit.services/lfk/frontend/commit/66e6cd80d39ef18a29fd8ac80fbac929bd0c4f8c)
- Merge pull request 'feature/14-team-management' (#36) from feature/14-team-management into dev [`4285168`](https://git.odit.services/lfk/frontend/commit/42851686caae69e6672f48cd7df77ee4c2e49092)
- 🧹 TeamsOverview - formatting [`6870a7f`](https://git.odit.services/lfk/frontend/commit/6870a7f9b1fce2f06182dafa502f6dc4bb818bd3)
- 🔒 ConfirmOrgDeletion in OrgOverview [`83f19a7`](https://git.odit.services/lfk/frontend/commit/83f19a7572255b5c095c68d688a963dbe3cf4a75)
- 🧹 Team cleanups [`07f2e65`](https://git.odit.services/lfk/frontend/commit/07f2e65fc722c0328ee5a8dc4d01fc89c906fa86)
- 🔒 ConfirmTeamDeletion in TeamDetail [`489244f`](https://git.odit.services/lfk/frontend/commit/489244f1a9636b9807e751073443f1c767f7b8ca)
- 🐞 fix deletion in TeamDetail + TeamsOverview [`e3943d8`](https://git.odit.services/lfk/frontend/commit/e3943d868a6131ea561c4000159b77ff70a4af8b)
- 🐞 fix component mount in TeamsEmptyState [`ace1a1b`](https://git.odit.services/lfk/frontend/commit/ace1a1b06379d922df2593c3a7aec77d5f958512)
- new license file version [CI SKIP] [`aaec5a3`](https://git.odit.services/lfk/frontend/commit/aaec5a3fc94e13c6d29ea16ab110151aa6dc1156)
- 🧹 drop tmp modification from UsersOverview [`854db4e`](https://git.odit.services/lfk/frontend/commit/854db4ece8388a70b8a50c5b4c7ce9be974a2616)
#### [0.3.0](https://git.odit.services/lfk/frontend/compare/0.2.1...0.3.0)
> 15 January 2021
- Merge pull request 'feature/16-org-management' (#32) from feature/16-org-management into dev [`#16`](https://git.odit.services/lfk/frontend/issues/16)
- Merge commit 'fcd657c10ea14290455cfb0bf2de89375a664143' into dev [`#31`](https://git.odit.services/lfk/frontend/issues/31)
- ✨ added Org base components [`bdc0de6`](https://git.odit.services/lfk/frontend/commit/bdc0de6adab2db95e6075a993bd50317e9e36eb5)
- 🚀RELEASE v0.3.0 [`7cd24cd`](https://git.odit.services/lfk/frontend/commit/7cd24cd51d52efff22f4ee0817f2d89f6efb35ed)
- 🏬 OrgDetail ui [`7a09869`](https://git.odit.services/lfk/frontend/commit/7a09869b0ca9d5dd38cf0c0010b71ab9ce6c6f40)
- 🏁 finish basic functionality of AddOrgModal + OrgDetail [`9324925`](https://git.odit.services/lfk/frontend/commit/93249258c6b0f34da22f0ed5d290b437d221a8fd)
- ✨ basic functionality in OrgDetail [`0e2a10f`](https://git.odit.services/lfk/frontend/commit/0e2a10fe94075b3cda8ef3ebae6a6159a2e6bbf9)
- 🐞 fix sidebar mobile-md scaling [`fcd657c`](https://git.odit.services/lfk/frontend/commit/fcd657c10ea14290455cfb0bf2de89375a664143)
- 🔨 reorder CI build order for correct license exporting [`7d58657`](https://git.odit.services/lfk/frontend/commit/7d58657c800e9e494c1d1f098ad0dc34d1f0cd8d)
- 💬 AddOrgModal bindings [`bc239ee`](https://git.odit.services/lfk/frontend/commit/bc239eead1d1a29c9662d94db797c6bf23e43166)
- ✨ improvements in OrgOverview [`0b9f3de`](https://git.odit.services/lfk/frontend/commit/0b9f3de47c0c8b750c38da07e53927ba55ef3499)
- 🔨 config compatibility for new Snowpack V3 bundler [`32ffa34`](https://git.odit.services/lfk/frontend/commit/32ffa345cde67b1affe2750add620522c0a24577)
- 🧹 general component cleanup [`157c7c6`](https://git.odit.services/lfk/frontend/commit/157c7c66b57c3a5053c780682402d3615fbc046a)
- ⏫ basic dependency bump [`6fc3c16`](https://git.odit.services/lfk/frontend/commit/6fc3c16073555fffd55773218c5c2eed361f076b)
- 🌎 i18n [`01c01a4`](https://git.odit.services/lfk/frontend/commit/01c01a46faa9332a2e437861fdb42afe962da833)
- ⏫ bump gridjs to 3.2.2 [`6ff9069`](https://git.odit.services/lfk/frontend/commit/6ff90694e288356e16f5226a6e0a054e6fec8b3f)
- [tmp] - disable serviceworker [`0fa107a`](https://git.odit.services/lfk/frontend/commit/0fa107a75bc33046758bc42523262a566da9f60f)
- new license file version [CI SKIP] [`ad34e45`](https://git.odit.services/lfk/frontend/commit/ad34e455ceedc0e0ccca5ccd86f7f011a7667873)
- ⏫ Bump Dockerfile builder to 15.5.1-alpine3.12 [`01fdd0b`](https://git.odit.services/lfk/frontend/commit/01fdd0bee2199bb58373aecc9942591a2d45011a)
- new license file version [CI SKIP] [`87926e6`](https://git.odit.services/lfk/frontend/commit/87926e69dbd5b93b2c98cd68fb31913259e3cc00)
- 🐳 Dockerfile - drop js sourcemaps [`4b80f30`](https://git.odit.services/lfk/frontend/commit/4b80f30afbfb8c16b9395fb310f309c516c48a17)
#### [0.2.1](https://git.odit.services/lfk/frontend/compare/0.2.0...0.2.1)
> 13 January 2021
- 🔒 UserDetail - WIP on Permissions [`36a084e`](https://git.odit.services/lfk/frontend/commit/36a084eab6bd0c922da29ebb2d260ba803bf9675)
- 👪 UserDetail - group edit support [`a9e319e`](https://git.odit.services/lfk/frontend/commit/a9e319e0c0f2bfaba42bc3af875462e394489dc2)
- 🙋‍♂️ UserDetails - group updating [`041c24a`](https://git.odit.services/lfk/frontend/commit/041c24a837d58ff46362699b54e8088f22ba2daa)
- ⚡ shared state reactivity - AddUserModal-Users-UsersOverview [`f7acbb1`](https://git.odit.services/lfk/frontend/commit/f7acbb1eaa14ddf41e29ff1db631520123218289)
- ✨ AddUserModal + UserDetail - optional username field [`7df76f9`](https://git.odit.services/lfk/frontend/commit/7df76f9642b528586fd654f27148b5502400ffdf)
- UserDetail - fixed group updating [`e6fbf7a`](https://git.odit.services/lfk/frontend/commit/e6fbf7aa5b230733ffd92fdfe851b9f86001c859)
- 🚀RELEASE v0.2.1 [`2bbaa50`](https://git.odit.services/lfk/frontend/commit/2bbaa500f4ca56e41613cac12228f4e3327180ab)
- 💬 UserDetail - info Toasts [`ea23b97`](https://git.odit.services/lfk/frontend/commit/ea23b972315db39e1f800366d52e3bacf9eaf58b)
- 🔨 optimized release script [`23c3cd6`](https://git.odit.services/lfk/frontend/commit/23c3cd605db950a5620ea709748216f7ea034385)
- 🐞 fix package release script: locales directory [`722a20e`](https://git.odit.services/lfk/frontend/commit/722a20e141079302da8876be1ccc55026c588048)
- ⏫ bump @odit/lfk-client-js to 0.0.11 [`f6db117`](https://git.odit.services/lfk/frontend/commit/f6db117a5eca5c9692a4c5d83159917a712a0e63)
- 🐞 UserDetail - fix permission reactivity by assignments [`39a3baa`](https://git.odit.services/lfk/frontend/commit/39a3baa00b0575ee3150c7ae26f32995e2e857d9)
#### [0.2.0](https://git.odit.services/lfk/frontend/compare/0.1.6...0.2.0)
> 11 January 2021
- 🔒 added rendering based on permission level [`5937a0d`](https://git.odit.services/lfk/frontend/commit/5937a0d7ce970d38ddb0e4c6a02d11f8c8b53e9b)
- ❌ UserDetail - delete [`4512272`](https://git.odit.services/lfk/frontend/commit/4512272c1c2a5c861d8ac4c9908244df125dc3d9)
- 🖊 UserDetail - reactivity on edit + update functionality [`ce1f384`](https://git.odit.services/lfk/frontend/commit/ce1f3842e0f581d9e94ea6079cf223d945f7465d)
- 🚀RELEASE v0.2.0 [`7769070`](https://git.odit.services/lfk/frontend/commit/77690702c0461b71ff1b65a44bfdc331d5a42c3e)
- [tmp] - disable darkmode + re-enable sw [`c7679b7`](https://git.odit.services/lfk/frontend/commit/c7679b7a67b362a7fbf796f612892bdfac50731c)
- 🕕 set manual refresh time to 2min [`be629e5`](https://git.odit.services/lfk/frontend/commit/be629e5c6b076bf9a28dcc953e97478d244d8413)
- 🐞 [tmp] - nginx.conf - disable .js file caching [`e6ac34b`](https://git.odit.services/lfk/frontend/commit/e6ac34bde8d288a45e83fc6723a2bbe952f2622a)
- update jwtinfo store on token refresh [`6356968`](https://git.odit.services/lfk/frontend/commit/63569684a392bf0c24c9c2efd7945114d1a230a5)
#### [0.1.6](https://git.odit.services/lfk/frontend/compare/0.1.5...0.1.6)
> 10 January 2021
- ✨ UsersOverview - user delete [`f0c100a`](https://git.odit.services/lfk/frontend/commit/f0c100aee47d6a5aeb0995db79b268f33bf316e9)
- 🔒 UserDetail - added basic layout for permission change [`81c1537`](https://git.odit.services/lfk/frontend/commit/81c1537bada2c87127385d9110d48459cd1b505f)
- 🚀RELEASE v0.1.6 [`ee01c3a`](https://git.odit.services/lfk/frontend/commit/ee01c3a059c435add68938657955cbd2c5851c50)
- 📧 UserDetail - email input [`f856c6a`](https://git.odit.services/lfk/frontend/commit/f856c6ae3792c93aee148339d89e3978db6e9293)
- ✨ UserDetail multiselect layout for groups [`98ecfab`](https://git.odit.services/lfk/frontend/commit/98ecfab0325e35c5418775f7162049b56e5f332f)
- UserDetail - placeholder for permission picker 🔒 [`b948b8c`](https://git.odit.services/lfk/frontend/commit/b948b8c1a48e5b4bc6a083139d26100ef4499970)
#### [0.1.5](https://git.odit.services/lfk/frontend/compare/0.1.4...0.1.5)
> 10 January 2021
- Merge commit '16f572480ad55425890061f9dad65fe85f2f39ad' into dev [`#30`](https://git.odit.services/lfk/frontend/issues/30)
- 🚀RELEASE v0.1.5 [`330755c`](https://git.odit.services/lfk/frontend/commit/330755c63e3405533a5da79c84ef4f379eb43e1c)
- ⤵ load dynamic build info in Footer component [`c089bb3`](https://git.odit.services/lfk/frontend/commit/c089bb39298fb1067093c2fa81101130c214947c)
- 📅 dynamic copyright year in Footer component [`b8a9e4f`](https://git.odit.services/lfk/frontend/commit/b8a9e4f272f925999b9a032dd009f7498acbfae0)
- 👀 improved Footer layout + display on Login component [`43b4065`](https://git.odit.services/lfk/frontend/commit/43b406592ebe115cea04a8dbf36874c0a5bdd7e9)
- ✨ added versionbuilder.js script [`1dd6674`](https://git.odit.services/lfk/frontend/commit/1dd6674faad686c0048a62b4cd25ab21b5c8b04f)
- ⚡ add versionbuilder script to release hook [`16f5724`](https://git.odit.services/lfk/frontend/commit/16f572480ad55425890061f9dad65fe85f2f39ad)
- 🔨 sample build of index.html with versionbuilder script [`3caa1fc`](https://git.odit.services/lfk/frontend/commit/3caa1fc277b99ee767c1e1fc4a28fd247d98e659)
#### [0.1.4](https://git.odit.services/lfk/frontend/compare/0.1.3...0.1.4)
> 10 January 2021
- Merge commit '45ec97066f425ac2ac66914be649cbd5a1038e10' into dev [`#20`](https://git.odit.services/lfk/frontend/issues/20)
- 🌎 add remaining translation keys for filepond [`45ec970`](https://git.odit.services/lfk/frontend/commit/45ec97066f425ac2ac66914be649cbd5a1038e10)
- add basic i18n logic to filepond [`b08c0f1`](https://git.odit.services/lfk/frontend/commit/b08c0f145a13d295b2a51c7a6da76faceb80a90c)
- 🚀RELEASE v0.1.4 [`4674b52`](https://git.odit.services/lfk/frontend/commit/4674b52717e388122e113553b8a136c649ce030c)
- 🌎 About - i18n [`692c906`](https://git.odit.services/lfk/frontend/commit/692c906cd26bdb7f7d730b90734d23748e0ab451)
- ✨ About - change license modal icon to "legal" [`0e31ba2`](https://git.odit.services/lfk/frontend/commit/0e31ba212f99d90b133bc242e5b4d163aa99b93b)
- new license file version [CI SKIP] [`4f3837a`](https://git.odit.services/lfk/frontend/commit/4f3837ac45a5df9a7476fd68ec9c069dc6b128cd)
#### [0.1.3](https://git.odit.services/lfk/frontend/compare/0.1.2...0.1.3)
> 10 January 2021
- 🌎 improved i18n for AddUserModal and UserDetail [`1c356a4`](https://git.odit.services/lfk/frontend/commit/1c356a41f54a954afaa69ed524f62c1676f7bbee)
- 🧹 simplified UsersOverview table [`e1d5d54`](https://git.odit.services/lfk/frontend/commit/e1d5d54cfb1fb20e56f0e971e2cfd196e9a913ac)
- 🌎 added i18n for UsersOverview [`3754f09`](https://git.odit.services/lfk/frontend/commit/3754f09b2f395a82ff8c3a9c655ab8a782e7eb71)
- 🖊 add basic UserDetail editing reactivity [`6012d05`](https://git.odit.services/lfk/frontend/commit/6012d0577e2ae6caf44aaee54335188bc767fff7)
- AddUserModal - data validation [`264e0d7`](https://git.odit.services/lfk/frontend/commit/264e0d7ed98c5000da543af154d6e36a6b956e4a)
- ✨ sample layout for advanced search [`5ec1dfa`](https://git.odit.services/lfk/frontend/commit/5ec1dfa8b0da4619f14e73794b9a9e22872aa330)
- userdetail - dynamic buttons [`ded3198`](https://git.odit.services/lfk/frontend/commit/ded31980bfbf9f09afa2818bdcc8cc3e40748441)
- 🚀RELEASE v0.1.3 [`ccb5125`](https://git.odit.services/lfk/frontend/commit/ccb5125a48486ef55709419eccd7d9e912a1e64c)
- 🔍 UsersOverview table - basic fuzzy search [`b9e0be4`](https://git.odit.services/lfk/frontend/commit/b9e0be448398d087005e220d08e34461490be14e)
#### [0.1.2](https://git.odit.services/lfk/frontend/compare/0.1.2-1...0.1.2)
> 10 January 2021
- Merge commit '2a37dfafa426e070aa136d171a1a01aa7f609d18' into dev [`#29`](https://git.odit.services/lfk/frontend/issues/29)
- Merge commit '5810b4ec4396ad650d90493fb48e2a8320865b42' into dev [`#4`](https://git.odit.services/lfk/frontend/issues/4)
- 🔒 added basic manual refresh every 4mins [`d92c6c0`](https://git.odit.services/lfk/frontend/commit/d92c6c0de9d6b72027b8aa27b22e3dc7b5116af1)
- 🚀RELEASE v0.1.2 [`242b5af`](https://git.odit.services/lfk/frontend/commit/242b5afbe93a6100826b0340f821ad2a2c4de343)
- dropped redundant console.log [`2a37dfa`](https://git.odit.services/lfk/frontend/commit/2a37dfafa426e070aa136d171a1a01aa7f609d18)
- 💾 save new auth data to localstorage [`2cbb431`](https://git.odit.services/lfk/frontend/commit/2cbb431acc0fe1aa333ddedb76510486a5fcf191)
- new license file version [CI SKIP] [`519ba79`](https://git.odit.services/lfk/frontend/commit/519ba79e1d5d97e2f59f769ef952a649481b55c0)
#### [0.1.2-1](https://git.odit.services/lfk/frontend/compare/0.1.2-0...0.1.2-1)
> 10 January 2021
- 🚀RELEASE v0.1.2-1 [`5810b4e`](https://git.odit.services/lfk/frontend/commit/5810b4ec4396ad650d90493fb48e2a8320865b42)
- 🧪 modified auto-changelog to commit CHANGELOG.md [`52aa996`](https://git.odit.services/lfk/frontend/commit/52aa99681bb02472e0433cb32b89dde814cd9467)
#### [0.1.2-0](https://git.odit.services/lfk/frontend/compare/0.1.1...0.1.2-0)
> 10 January 2021
- 🧪 experimental - auto changelog generation [`b28f76a`](https://git.odit.services/lfk/frontend/commit/b28f76a1d46a7abdd3014398d41ff976bf39230b)
- 🚀RELEASE v0.1.2-0 [`0276c3d`](https://git.odit.services/lfk/frontend/commit/0276c3deeb8d78f9773bea724e4007f03e8de5e4)
#### [0.1.1](https://git.odit.services/lfk/frontend/compare/0.1.0...0.1.1)
> 10 January 2021
- 🚀RELEASE v0.1.1 [`a66f6bb`](https://git.odit.services/lfk/frontend/commit/a66f6bbec8e9d6be4fd0b68e0a388d351dbc5cb1)
- 🧪 move changelog generation to default [`139b329`](https://git.odit.services/lfk/frontend/commit/139b3294cdfd2437605142de0e4bb029f03f54f6)
#### 0.1.0
> 10 January 2021
- Merge commit '2657f30cf3acaa592408d2d4cddcb02bf76bb6af' into dev [`#27`](https://git.odit.services/lfk/frontend/issues/27)
- Merge commit '80d30a8e5425f4041e79c299095c36386b8d7777' into dev [`#26`](https://git.odit.services/lfk/frontend/issues/26)
- 📦 further Dockerfile/ Bundle optimizations [`#25`](https://git.odit.services/lfk/frontend/issues/25)
- Merge commit 'bb0eb6d1e276186af2c1e5d26abda4413c278981' into dev [`#11`](https://git.odit.services/lfk/frontend/issues/11)
- Merge branch 'feature/24-i18n-formatting' into dev [`#24`](https://git.odit.services/lfk/frontend/issues/24)
- Merge branch 'feature/17-license_collection' into dev [`#17`](https://git.odit.services/lfk/frontend/issues/17)
- Merge pull request 'feature/10-dashboard-stats' (#21) from feature/10-dashboard-stats into develop [`#10`](https://git.odit.services/lfk/frontend/issues/10)
- Merge pull request 'feature/8-login-preserve' (#19) from feature/8-login-preserve into develop [`#8`](https://git.odit.services/lfk/frontend/issues/8)
- Merge pull request 'feature/5-base-components' (#18) from feature/5-base-components into develop [`#5`](https://git.odit.services/lfk/frontend/issues/5)
- Merge commit 'de7cd1267df684b5befd6111aa347e76c6f2ffdd' into develop [`#7`](https://git.odit.services/lfk/frontend/issues/7)
- 🌑 enable tailwind darkmode in config [`#6`](https://git.odit.services/lfk/frontend/issues/6)
- basic move to snowpack [`#3`](https://git.odit.services/lfk/frontend/issues/3)
- tailwind config [`#2`](https://git.odit.services/lfk/frontend/issues/2)
- license.json export + usage [`9ab72ae`](https://git.odit.services/lfk/frontend/commit/9ab72aed19f4174abdff83e45d9f3bfd16448ad4)
- basic User Components [`08278b3`](https://git.odit.services/lfk/frontend/commit/08278b36a5ed8096ae1d8eed6d10dcc1abdc5943)
- general page cleanups [`3f961dd`](https://git.odit.services/lfk/frontend/commit/3f961dd3bd559968626ee9cacb2b4fb00f776a64)
- demo run [`70e10f7`](https://git.odit.services/lfk/frontend/commit/70e10f7a70cf24fe4b78eee91bf4af3e37452fff)
- basic AddUserModal ui [`e5ec98b`](https://git.odit.services/lfk/frontend/commit/e5ec98bf6f599fe7fcf53065666cff880e0d5dfd)
- working on user detail page [`0e08c7f`](https://git.odit.services/lfk/frontend/commit/0e08c7f075571c8734bcda3347e2e2635c46c806)
- simplified css classes for cleanliness [`9684c22`](https://git.odit.services/lfk/frontend/commit/9684c22da3c9029d00057a3efe9a0fe34fd41070)
- first UserDetail page mockup [`d570336`](https://git.odit.services/lfk/frontend/commit/d5703365e46e52ee5b3090a89526a887db9ffc0d)
- AboutPage - read license text modal [`c0328c5`](https://git.odit.services/lfk/frontend/commit/c0328c5cdb1edc1c3dca368d65b84e640ef4df3c)
- fixed datatable-emptystate transition [`10bf88e`](https://git.odit.services/lfk/frontend/commit/10bf88e4ba2390dbf78925f0541c49a4abe3041d)
- working on AddUserModal [`cb58fdf`](https://git.odit.services/lfk/frontend/commit/cb58fdfd8ed6c20551d99cefe37e10e9e46f13bf)
- display full user names in overview table [`6529907`](https://git.odit.services/lfk/frontend/commit/6529907a139edd8450e699a8ea9befae622137f7)
- reactivity on adding a new Track [`f37ba1d`](https://git.odit.services/lfk/frontend/commit/f37ba1dbf7cc71573bcfbc1fe09c2a4b9d0d3404)
- User Overview table expansion [`58d68c8`](https://git.odit.services/lfk/frontend/commit/58d68c8324b6306be697cde0be53f0c5caf262aa)
- basic track deletion working ✅ [`35a9aa4`](https://git.odit.services/lfk/frontend/commit/35a9aa40cbbb86bd004cf18792951dd3f81fc859)
- Track edit animation [`e5fcb2e`](https://git.odit.services/lfk/frontend/commit/e5fcb2ef68cdbc55875d5bf7f97ddfe297abe7e1)
- basic edit logic in table [`039fd8f`](https://git.odit.services/lfk/frontend/commit/039fd8f90ebccf8594c608c4cd7c8fe5178c66d0)
- ⚡ PWA optimizations [`bc66ebb`](https://git.odit.services/lfk/frontend/commit/bc66ebbf0aa2a756829b92a0869b60c093f5e7f8)
- UserDetailOne sample ui [`23e03be`](https://git.odit.services/lfk/frontend/commit/23e03bec352a9ec53cb3962b4f6ec64b0ea643ad)
- improved sidebar icons [`5c218c6`](https://git.odit.services/lfk/frontend/commit/5c218c64abc3e54fcc00ba996a9b4749d3340c75)
- added Breadcrumb nav to UserDetail [`11457b2`](https://git.odit.services/lfk/frontend/commit/11457b2792dca55225280854456481b7c6978071)
- basic ForgotPassword improvements [`e8c98a0`](https://git.odit.services/lfk/frontend/commit/e8c98a0a29518063d4f573a3c6a1055f3bf18906)
- 🎨 general page styles [`02d8888`](https://git.odit.services/lfk/frontend/commit/02d8888d97da0125d47269bd8526c94535ff1efd)
- AddTrackModal - basic validation w/ class + layout responses [`591dc09`](https://git.odit.services/lfk/frontend/commit/591dc09228408f4fff0349a42807c1d6268a59e3)
- general dependency bump ⏫ [`6e4fe37`](https://git.odit.services/lfk/frontend/commit/6e4fe37378ab4af8e438fa7d0c7cf43fc45b2c99)
- edit is working 🎉 [`68de076`](https://git.odit.services/lfk/frontend/commit/68de0762271eb9dbe0e7135f6457cf0b09d889d6)
- move serviceworker registration to separate module [`8f25a87`](https://git.odit.services/lfk/frontend/commit/8f25a874cb2f7fc3181c356514e9e6c11136c9bc)
- 🌍 i18n [`8443085`](https://git.odit.services/lfk/frontend/commit/84430854df8581b43eb48a153b52030cb1b384f1)
- AddTrackModal action [`501bf7a`](https://git.odit.services/lfk/frontend/commit/501bf7a5f4895f5b821abfe62830602171a57655)
- 🌍 translations [`48ddc62`](https://git.odit.services/lfk/frontend/commit/48ddc621921f796a02c0bf74d5721a7851cd1523)
- general cleanups [`5b15141`](https://git.odit.services/lfk/frontend/commit/5b15141ecc0a5ed6a689e739b0f8990be244fa53)
- 🐞👀 visual fix in Footer component [`0c7bc07`](https://git.odit.services/lfk/frontend/commit/0c7bc07d67f7449e51bbb3d5f1f7af3241aa2839)
- include minimum lap times [`7210f1b`](https://git.odit.services/lfk/frontend/commit/7210f1b947de01ca6f3ebad600744fd49b0a561f)
- general + PWA optimizations [`43ecd83`](https://git.odit.services/lfk/frontend/commit/43ecd83213abe401f324e058bd255ce0b76f553c)
- improved order script for scalability [`947482c`](https://git.odit.services/lfk/frontend/commit/947482c1b51ad1b5a055eddf112e6aa6b88f5939)
- i18n 🌍 [`1c2636d`](https://git.odit.services/lfk/frontend/commit/1c2636d6693cafe9011d24c1cbdefc9a3eaa008c)
- delete confirmation [`7d1b519`](https://git.odit.services/lfk/frontend/commit/7d1b51918f5fee8791d1bd4e36056c4d979a2a96)
- Component + Login cleanup [`b111463`](https://git.odit.services/lfk/frontend/commit/b1114634e804b0f41b80c87afa6903ffc17f575c)
- AddTrackModal - more input validation response [`fe297f6`](https://git.odit.services/lfk/frontend/commit/fe297f67795c35f3ea0e9a88b8453b27a4de05e0)
- UserOverview cleanup [`71c7611`](https://git.odit.services/lfk/frontend/commit/71c761187f0e645ef06808fac930045589cb7c1a)
- AddTrackModal - minlaptime validation [`bb0eb6d`](https://git.odit.services/lfk/frontend/commit/bb0eb6d1e276186af2c1e5d26abda4413c278981)
- AddTrackModal - add track icon [`7bb5a18`](https://git.odit.services/lfk/frontend/commit/7bb5a18527994f4fe3f23a87106a431f1ebfe7d1)
- StatCards direct linking to detail page [`9527167`](https://git.odit.services/lfk/frontend/commit/9527167fbc8774d57fce206b40800f249fd50ed8)
- added basic release-it config [`52a19c2`](https://git.odit.services/lfk/frontend/commit/52a19c2036e6d928c790f914f53ebd614d0e1bea)
- move FormLayout component to Settings Page [`9309ea9`](https://git.odit.services/lfk/frontend/commit/9309ea9a30a062ecb6ddc4152174ca9371fd32b8)
- logic cleanup 🧠 [`59d5ad5`](https://git.odit.services/lfk/frontend/commit/59d5ad5bd32ad45ccfb50ab4ae4dfe47db182113)
- use outsideclick custom directive in AddTrackModal component [`2657f30`](https://git.odit.services/lfk/frontend/commit/2657f30cf3acaa592408d2d4cddcb02bf76bb6af)
- 🐳 added Docker buildsteps for sw generation [`aaef97d`](https://git.odit.services/lfk/frontend/commit/aaef97dd431d27dbab023fd05c281b8b35a1b6df)
- improved empty state [`6e00ac8`](https://git.odit.services/lfk/frontend/commit/6e00ac8f1f9bcc9ac714177c5ab2d1d04854aaa5)
- AddTrackModal padding style [`d830727`](https://git.odit.services/lfk/frontend/commit/d830727036a5bf0f7d81efba2d15ac4394ea82e4)
- added general PromiseError component [`9e19c48`](https://git.odit.services/lfk/frontend/commit/9e19c482584062380403d505be28c1ce13a7f442)
- basic formatting script [`f9aa262`](https://git.odit.services/lfk/frontend/commit/f9aa262cab64596185323dbf5fb96e70019bf126)
- 🌍 i18n lap time [`dadccc1`](https://git.odit.services/lfk/frontend/commit/dadccc1b5f7e38adeae8c988efe1ad63252f7599)
- added routing to UserDetail page [`3c4a109`](https://git.odit.services/lfk/frontend/commit/3c4a10944ebcf7a627f2749e21572342059452da)
- added outsideclick as custom directive [`a042c8a`](https://git.odit.services/lfk/frontend/commit/a042c8a870323262803989df840f15c351393a24)
- added middlename input field to mock [`8cf73a2`](https://git.odit.services/lfk/frontend/commit/8cf73a2be0ae5cb842d78ae2071fdc54bb18c4bc)
- re-enable Tailwind compile with postcss [`6915123`](https://git.odit.services/lfk/frontend/commit/6915123973460fea5a7e6934404dc5906114a812)
- display profilepic properly [`4fd1ac2`](https://git.odit.services/lfk/frontend/commit/4fd1ac28c58366d6d719942a2953d093b3829a01)
- general dependency bump ⏫ [`f7d7b83`](https://git.odit.services/lfk/frontend/commit/f7d7b837c457e6ed324f3512979506e96386b207)
- Login Component Accessibility 👀 [`fc5c8d1`](https://git.odit.services/lfk/frontend/commit/fc5c8d13093023632129134cbae702cd840e95c2)
- NGINX config - enable brotli compression [`1f50574`](https://git.odit.services/lfk/frontend/commit/1f5057438fa75e3c5ff3fd76f4bb52658f4b74ec)
- remove duplicate class usage in Dash Component [`dbc660c`](https://git.odit.services/lfk/frontend/commit/dbc660c48e33d98b8c7e0c5ddb33729c0cc54799)
- ❌ escape key support [`0c3e74f`](https://git.odit.services/lfk/frontend/commit/0c3e74f8a3f04997faa3f1aa10926f09972ac3f6)
- 🚀RELEASE v0.1.0 [`1aac783`](https://git.odit.services/lfk/frontend/commit/1aac783df351526787ba8aff12c013a8ab06524f)
- ▶ ENTER key submit [`80d30a8`](https://git.odit.services/lfk/frontend/commit/80d30a8e5425f4041e79c299095c36386b8d7777)
- EmptyState fixup [`02bfecd`](https://git.odit.services/lfk/frontend/commit/02bfecd54015ad6c6de25c246552feeab5c084ef)
- 🎨 OrgDetail style fix [`3a702aa`](https://git.odit.services/lfk/frontend/commit/3a702aa91e768ab58e017d859732fcac960edac6)
- re-added missing i18n keys [`17e7778`](https://git.odit.services/lfk/frontend/commit/17e7778d15380a2bd3d2f0bb38ed9c69bcb0bdce)
- ⏫ bump to @odit/license-exporter@0.0.9 for version support [`919910d`](https://git.odit.services/lfk/frontend/commit/919910d4a8c7ee2876b51de78b72311230476a32)
- AddTrackModal - autofocus on modal open [`388fc8f`](https://git.odit.services/lfk/frontend/commit/388fc8f239560be221687c69bfbffd9e1b3e56e1)
- AddTrackModal - trap focus if active [`0e7640b`](https://git.odit.services/lfk/frontend/commit/0e7640bf86b6710d01cd6c553f205cad934f58bb)
- ⏫ bump lfk client [`236aba8`](https://git.odit.services/lfk/frontend/commit/236aba89c2967c38402b909e65ad724e27144f17)
- ⏫ dependency bump [`ba87349`](https://git.odit.services/lfk/frontend/commit/ba87349a88a54152874b6a30218042a676799c68)
- 🌍 i18n [`281747a`](https://git.odit.services/lfk/frontend/commit/281747a681624ecaeee902910f1ff3c52f9e3155)
- move OpenAPI config to App component rather than Login [`b107f5d`](https://git.odit.services/lfk/frontend/commit/b107f5de95489b02fe73e8fdc2836b02d1d29d02)
- NGINX config - fix 404 error for SPA usage [`32e4f22`](https://git.odit.services/lfk/frontend/commit/32e4f223f89d2b58ffc2466aded008fa1c948290)
- 🐞fixed snowpack config for bundle optimization [`3e8d0b5`](https://git.odit.services/lfk/frontend/commit/3e8d0b54627f175bfe29bfc14308fa433e2c72af)
- 🐞 fix malfuntion in logout logic [`7cf2ffc`](https://git.odit.services/lfk/frontend/commit/7cf2ffce2d2b7905388169a9c38462337699b5e5)
- actually perform user logout (recreate Cookies, invalidate token) [`2048533`](https://git.odit.services/lfk/frontend/commit/2048533fda6da6fdc0aeb0f42f3c6a66d7e87911)
- proper middlename display support [`644b9a7`](https://git.odit.services/lfk/frontend/commit/644b9a76836c626fb3581a352ae53c72cd99b5b0)
- ignore licenses.json in serviceworker [`c38d33a`](https://git.odit.services/lfk/frontend/commit/c38d33a549143143d08ccbe5b82f14174eaebf70)
- dependency bump ⏫ [`9aeb99d`](https://git.odit.services/lfk/frontend/commit/9aeb99d775e1bb7a8a26d68836dd8d4a4b085406)
- Dashboard - fix accessibility focus state on nav Dropdown + Logout [`44029c8`](https://git.odit.services/lfk/frontend/commit/44029c812eebe3ee591dc2133c35082c5b3de41f)
- move toastify css to app base [`34ecb8b`](https://git.odit.services/lfk/frontend/commit/34ecb8b2fb19d243db35bd59f98d93f08d522f25)
- ⏫ tinro dependency bump [`e62277b`](https://git.odit.services/lfk/frontend/commit/e62277bdd2492119dd78a41d6cb3c415c7ce69b8)
- new layout margin from page title [`51c9c3f`](https://git.odit.services/lfk/frontend/commit/51c9c3fe3c7dc3d6f4d5665e3eac1fbc6f837659)
- add basic package script [`e361c89`](https://git.odit.services/lfk/frontend/commit/e361c89f6c6b719e4a7fa1b0db23885249cd3b2b)
- added release-it dev dependency [`0023e22`](https://git.odit.services/lfk/frontend/commit/0023e225243af7b6e2d85ee18522ee7f67828b73)
- AddTrackModal - placeholder input [`d45eec9`](https://git.odit.services/lfk/frontend/commit/d45eec94ab7386ef29170b03e73c7683b36a3a0d)
- add package script [`1cd03ef`](https://git.odit.services/lfk/frontend/commit/1cd03ef02748f55b8e66e5e26b3e2be64b659313)
- track lap time interactive placeholder [`008c91a`](https://git.odit.services/lfk/frontend/commit/008c91a5526edc51d4995565f1681f300f980b51)
- Merge commit '3a702aa91e768ab58e017d859732fcac960edac6' into feature/11-tracks-management [`8af63fc`](https://git.odit.services/lfk/frontend/commit/8af63fc22a47959859153f8e742df86f73b9be54)
- init [`32357ec`](https://git.odit.services/lfk/frontend/commit/32357ece0a7195ea1135c9c3e4c6c84323f95b4d)
- tmp [`1b7173c`](https://git.odit.services/lfk/frontend/commit/1b7173cda9134ee8058a00bdc030defa80d46bfc)
- Login - move to env.js import [`8ef0b21`](https://git.odit.services/lfk/frontend/commit/8ef0b21819309752c573d0485f6514152fb684e6)
- initial commit [`4bb3bae`](https://git.odit.services/lfk/frontend/commit/4bb3bae4e6fc89c35a8a2b36b7cd6e6d47958eae)
- Initial license export [`4c96b9a`](https://git.odit.services/lfk/frontend/commit/4c96b9a3e04dbb7c021c71aa8828a29248509fbe)
- 🚚 move to tinro svelte router [`a50ea15`](https://git.odit.services/lfk/frontend/commit/a50ea15b38023b867a9f7757e973184cbcdd2457)
- new Dashboard [`7270ce9`](https://git.odit.services/lfk/frontend/commit/7270ce9d32869abd4f6ac65ab7c2c87363633cbe)
- Tracks sample page w/ modal [`c2cf8a0`](https://git.odit.services/lfk/frontend/commit/c2cf8a098974c6f2e75690a5f767a8282fd95789)
- Profile page [`d580522`](https://git.odit.services/lfk/frontend/commit/d5805229b3dfed37a3a210fcbcc1435fe1b6952e)
- PwReset view [`98bc810`](https://git.odit.services/lfk/frontend/commit/98bc810e515fc04192e6058e0dce272a2e34e76a)
- StatCards - fetch real data from API [`3d93b04`](https://git.odit.services/lfk/frontend/commit/3d93b04264e99f4717e48e68136a08c85e54d4a0)
- general UI cleanup [`0e5fbb8`](https://git.odit.services/lfk/frontend/commit/0e5fbb8835331966c23522d831be2a6a08ae123f)
- Dashboard component cleanup [`89fcfe6`](https://git.odit.services/lfk/frontend/commit/89fcfe6a4903f77360257883b11fce689de9f5cb)
- improved About page + route [`45cd532`](https://git.odit.services/lfk/frontend/commit/45cd532c1ae322759b7fe5393bb27c17c3c03f83)
- 🌎 first i18n support [`2c77450`](https://git.odit.services/lfk/frontend/commit/2c774508848d918b0348cac5c81c376952c07ae5)
- i18n fix + more pages [`4633a9b`](https://git.odit.services/lfk/frontend/commit/4633a9b3aeec136ad6f434fa46a3925b37ac5524)
- i18n compatible datatable [`3c36bea`](https://git.odit.services/lfk/frontend/commit/3c36bea07cdd2bf690e93fe8ddfbf5be62ddc7cb)
- more translations [`1dc4983`](https://git.odit.services/lfk/frontend/commit/1dc498302f9ab0563187439a7c207b00f0af3acb)
- tmp [`8ff1c55`](https://git.odit.services/lfk/frontend/commit/8ff1c55ba4e52f36931eb53aaa18021a1b894a7a)
- Tags [`3bd954e`](https://git.odit.services/lfk/frontend/commit/3bd954e9ffe558495427b899e0f33baffc9d227d)
- About page [`a977b34`](https://git.odit.services/lfk/frontend/commit/a977b34adbf5b95b8813ab0a18252dab7f3a8a75)
- ForgotPassword page [`25e62f0`](https://git.odit.services/lfk/frontend/commit/25e62f09075dbb0150d48698d8dd4ba4bb9530e3)
- Sidebar [`4765fae`](https://git.odit.services/lfk/frontend/commit/4765faeddc3e90c3d8696595f1db223c97e4b890)
- ✨ Avatars [`7e69eee`](https://git.odit.services/lfk/frontend/commit/7e69eeef295901749dcb2a9b5d57a40959288fa4)
- 🖼 new icons + i18n for card titles [`4f02a5b`](https://git.odit.services/lfk/frontend/commit/4f02a5bac0b12f89cfa95e8effc65d5b9cdad732)
- basic dependencies + first layout [`3ce8284`](https://git.odit.services/lfk/frontend/commit/3ce82840783e24ed605837fbde76f5562f1256c1)
- 💡 sample card layout + data [`20ee0c1`](https://git.odit.services/lfk/frontend/commit/20ee0c1a9efe588a8a24d47cc345c24e76c02e6f)
- Breadcrumb component [`815e07c`](https://git.odit.services/lfk/frontend/commit/815e07c13262607f2f0e701423b8b85d7996cf9c)
- ✨ Badges [`2fa102b`](https://git.odit.services/lfk/frontend/commit/2fa102b5430cbd57dfb63823450c15abd71bbd3b)
- first working router 🎉 [`b4d1370`](https://git.odit.services/lfk/frontend/commit/b4d13701a7ee16ad0281271b233eb03632673ffc)
- Dashboard code cleanup [`9b036de`](https://git.odit.services/lfk/frontend/commit/9b036de4d7adce065ff454b3183dcd78866a2363)
- 💾 login state preserve (localstorage) + JWT payload parsing [`13ccb56`](https://git.odit.services/lfk/frontend/commit/13ccb56354135704834ffade8085ddbc8c03b369)
- profile actions dropdown [`a80d3b0`](https://git.odit.services/lfk/frontend/commit/a80d3b060faad61f625776e23739f4b2837927c5)
- Added drone file with pipeline for dev [`9bf21a0`](https://git.odit.services/lfk/frontend/commit/9bf21a0eeb6d32cab18dcbb55f571969773fffac)
- DataTable [`d293859`](https://git.odit.services/lfk/frontend/commit/d2938591bf157e88acfd274ff1dfd527a1ce8588)
- 🚧 move to new config based env [`2cdd1cd`](https://git.odit.services/lfk/frontend/commit/2cdd1cd96196f586abfa50ba2f2b05f99545c669)
- Dashboard - dropdown arrow change [`b27fb8c`](https://git.odit.services/lfk/frontend/commit/b27fb8c0b5532710c97947731f0725e37f13c579)
- filepond integration ✅ [`ada6888`](https://git.odit.services/lfk/frontend/commit/ada68887a2e6a4a552c1d093f5ec1afaebd918d3)
- more translations [`186803f`](https://git.odit.services/lfk/frontend/commit/186803f8216c991927d8f5c8ec6aa19016267858)
- general cleanups [`428d2e2`](https://git.odit.services/lfk/frontend/commit/428d2e25790e7509c535970accbe9f79fbf6ab65)
- 🐳 move to new + simple Dockerfile [`9202b47`](https://git.odit.services/lfk/frontend/commit/9202b473f579c50f9a0b269ee2ef9530cd690d2c)
- TracksOverview route [`f3f318f`](https://git.odit.services/lfk/frontend/commit/f3f318fd01372eea1edff265d678f1a03e252377)
- Sidebar - UX logic w/ interactivity [`773800e`](https://git.odit.services/lfk/frontend/commit/773800e73203d438fe2f3e8245b6d13e9407f996)
- Login component - enter key listener [`016f08b`](https://git.odit.services/lfk/frontend/commit/016f08b07cf2b40a53a57642d7bc75ebd669feab)
- 🐳 new Dockerfile [`33d8622`](https://git.odit.services/lfk/frontend/commit/33d8622eb27d284dddb9bc14740e7a90b4b809d5)
- AddTrackModal cleanup [`f5d0e28`](https://git.odit.services/lfk/frontend/commit/f5d0e285fb3a913b7a26bf2ce7b7621502b68e49)
- Footer component cleanup [`05306c7`](https://git.odit.services/lfk/frontend/commit/05306c75a555d38adad3f3fc4a4d3bd1211c626c)
- ♻️ drop svelte-spa-router [`46491f3`](https://git.odit.services/lfk/frontend/commit/46491f38a0cf8d2ef3ceac00ce1be289a594eade)
- 🚧 working on sidebar [`e4c6c57`](https://git.odit.services/lfk/frontend/commit/e4c6c5711d55e8acb4366cebbd9c8b7c45741c5e)
- added webmanifest 🧾 [`de7cd12`](https://git.odit.services/lfk/frontend/commit/de7cd1267df684b5befd6111aa347e76c6f2ffdd)
- basic service worker registration via workbox [`13b6b32`](https://git.odit.services/lfk/frontend/commit/13b6b320411a4fda24c49cdec5fd890d09d02c9c)
- more i18n usage + logo [`d817058`](https://git.odit.services/lfk/frontend/commit/d817058e515cf79c782446c4b8c9443b15211523)
- ✨ Tabs [`e6815eb`](https://git.odit.services/lfk/frontend/commit/e6815eb75f45b56a8404a24d0d96b3e79ba73834)
- Dashboard - use sample components [`b2b05a4`](https://git.odit.services/lfk/frontend/commit/b2b05a4ec9f565ec92ebbf84ad4ed5f1eb80cbfe)
- Tracks page cleanup [`9af4cfe`](https://git.odit.services/lfk/frontend/commit/9af4cfe73f92f28e42c0d2bd83f687be16d4b8f0)
- Track page cleanup [`72f1645`](https://git.odit.services/lfk/frontend/commit/72f1645767ab57a6ce94bbe80eff2858a113bace)
- first language support [`35d379d`](https://git.odit.services/lfk/frontend/commit/35d379d84303cfb119c8df97d8ad0222e9749826)
- cleaned up Login component for darkmode compatibility [`040359a`](https://git.odit.services/lfk/frontend/commit/040359aa933bdfd3d2119229d29c95aa5a308a86)
- Tracks datatable action ui [`9975c0c`](https://git.odit.services/lfk/frontend/commit/9975c0cf64c356c30e229127bfa6d04df1b41a4a)
- hash based routing in env file [`3396d17`](https://git.odit.services/lfk/frontend/commit/3396d173586a6bb9518d07e4f9d84b6ba0bcf4b9)
- 🌎 translations [`0ff0a29`](https://git.odit.services/lfk/frontend/commit/0ff0a29dfe73c38b1c0c2dfb9b1e92a1b90e6c9c)
- dependency bumps ⏫ [`0dc8bff`](https://git.odit.services/lfk/frontend/commit/0dc8bffaec36b5f0419f84ee5722805b0d329f86)
- 🧠 logic cleanup in Login component [`90be420`](https://git.odit.services/lfk/frontend/commit/90be4200ca2578fe6b7f516b6c2b427e4875694a)
- demo login [`5499669`](https://git.odit.services/lfk/frontend/commit/5499669564801aab526a54cac4dca154e0fac6a4)
- [tmp] - move to cdn usage [`1473267`](https://git.odit.services/lfk/frontend/commit/1473267e8c7fd303c67ae8044c9846cd5ec94f9c)
- 🧹 drop nanoid dependency [`71b6258`](https://git.odit.services/lfk/frontend/commit/71b6258bc36576b4d9ad61556661ba299831e988)
- updated Dockerfile for snowpack ❄ [`6f787f6`](https://git.odit.services/lfk/frontend/commit/6f787f63f7530d6da94195e0e148b97f581b0bed)
- add Tracks route to app [`728dd40`](https://git.odit.services/lfk/frontend/commit/728dd40f16f11dd80275388cff75e2971172eb5a)
- small bugfix [`b9410dc`](https://git.odit.services/lfk/frontend/commit/b9410dc5f1611f274c882e74312e2187757be246)
- 🌍 i18n - general_promise_error [`f1b2c1d`](https://git.odit.services/lfk/frontend/commit/f1b2c1de267dd0388de293e481d2739a3eb64d38)
- move filepond import to css-in-js [`e48502f`](https://git.odit.services/lfk/frontend/commit/e48502fb41f483685e24b8d054406a8cc7459451)
- Dashboard - demo urls [`c65c138`](https://git.odit.services/lfk/frontend/commit/c65c138830f883d85c897478dc1c44ef527f195e)
- gitignore yarn.lock package-lock.json [`112516c`](https://git.odit.services/lfk/frontend/commit/112516ccfd6f00014c611cc14bf5a4bb431a9bca)
- ⏫ dependency bump [`1104ab2`](https://git.odit.services/lfk/frontend/commit/1104ab20c7de0ab5c97e0dc636835b2a4e25ed6b)
- Updated dev branch name [`b538b6c`](https://git.odit.services/lfk/frontend/commit/b538b6cb7103b47ef7bc34c04087580918c09f6d)
- Added script for license export [`1fba3ef`](https://git.odit.services/lfk/frontend/commit/1fba3ef9e43f3d9302409446d6ed2a641e554d7c)
- added translations 🌍 [`b4df123`](https://git.odit.services/lfk/frontend/commit/b4df123c0402ab7ae42fd8289ae9bb2f3fff9d5e)
- ✨ NotFound [`d188ae6`](https://git.odit.services/lfk/frontend/commit/d188ae658af7541764d34884e8ca67a59f0955c2)
- DataTable - load from json object [`a379327`](https://git.odit.services/lfk/frontend/commit/a379327d67ba5e9bab1724cf7e170c01166b4d64)
- ⏫ dependency bump svelte [`905d8b0`](https://git.odit.services/lfk/frontend/commit/905d8b0a201631cdca462a8a33d5fd394db36305)
- favicon + title [`d2b2542`](https://git.odit.services/lfk/frontend/commit/d2b254241295fa177af01a9f2f0fb747a7f9f02e)
- 🎨 TailwindStyles - Chromium fix [`9fbe1c7`](https://git.odit.services/lfk/frontend/commit/9fbe1c7c6cce21ba536128ceaa35b72c7841f67f)
- 👋 sample user info usage on dashboard [`813c058`](https://git.odit.services/lfk/frontend/commit/813c058a9a805bc2f63586e50c5b949d33406654)
- 🔧 re-enable jwtinfo userdetails in Dashboard [`12eb207`](https://git.odit.services/lfk/frontend/commit/12eb207605f69598a9a2308cf9b1c2d4916f8cb3)
- Dependency: Bumped license-exporter version [`0842640`](https://git.odit.services/lfk/frontend/commit/0842640fec285a3d99f0c7551f008919e64e5dcd)
- move StatCards up in Dashboard UI [`e9a6637`](https://git.odit.services/lfk/frontend/commit/e9a6637eb9f303dc93dbbc3287f9b7b4d92ed42a)
- 🐞 translation fix [`517c638`](https://git.odit.services/lfk/frontend/commit/517c6381bae270346c8ba60dd3287d96d17d4bf3)
- Dashboard - hide overflow [`4c161b3`](https://git.odit.services/lfk/frontend/commit/4c161b3c7067ed042deeea3bea0ef24b306c94a1)
- ⏫ bump client to 0.0.5 [`9633a09`](https://git.odit.services/lfk/frontend/commit/9633a09b730be91c31419cd8bfcea108c82976d3)
- ⏫ Bump snowpack to 3.0.0-rc.2 🧪❄ [`9a53e32`](https://git.odit.services/lfk/frontend/commit/9a53e326917bc1b9756baf2ce511447974779c8f)
- ✒ package rename [`937d044`](https://git.odit.services/lfk/frontend/commit/937d044b59095192859d516fbf60c04b057562be)
- add theme color to index [`fe40fd9`](https://git.odit.services/lfk/frontend/commit/fe40fd9a91f634046a974a90986e915ee3c53cc0)

View File

@ -1,12 +1,18 @@
FROM node:15.4.0-alpine3.12 FROM node:15.5.1-alpine3.12
WORKDIR /app WORKDIR /app
RUN npm i -g pnpm RUN npm i -g pnpm
COPY package.json index.html ./ COPY package.json ./
RUN pnpm i RUN pnpm i
COPY package.json *.config.js workbox-config.js template-copy.js index.template.html s-config.template.js ./
COPY src ./src COPY src ./src
COPY public ./public COPY public ./public
RUN pnpm run build RUN pnpm run build
# final image # final image
FROM alpine
COPY --from=0 /app/build /app
RUN rm -rf /app/build/_dist_/components
RUN rm -rf /app/build/_dist_/locales
RUN rm -rf /app/build-manifest.json
FROM fholzer/nginx-brotli:v1.19.1 FROM fholzer/nginx-brotli:v1.19.1
COPY --from=0 /app/dist /usr/share/nginx/html COPY --from=1 /app /usr/share/nginx/html
COPY ./nginx.conf /etc/nginx/nginx.conf COPY ./nginx.conf /etc/nginx/nginx.conf

362
LICENSE Normal file
View File

@ -0,0 +1,362 @@
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Creative
Commons Corporation ("Creative Commons") is not a law firm and does not provide
legal services or legal advice. Distribution of Creative Commons public licenses
does not create a lawyer-client or other relationship. Creative Commons makes
its licenses and related information available on an "as-is" basis. Creative
Commons gives no warranties regarding its licenses, any material licensed
under their terms and conditions, or any related information. Creative Commons
disclaims all liability for damages resulting from their use to the fullest
extent possible.
Using Creative Commons Public Licenses
Creative Commons public licenses provide a standard set of terms and conditions
that creators and other rights holders may use to share original works of
authorship and other material subject to copyright and certain other rights
specified in the public license below. The following considerations are for
informational purposes only, are not exhaustive, and do not form part of our
licenses.
Considerations for licensors: Our public licenses are intended for use by
those authorized to give the public permission to use material in ways otherwise
restricted by copyright and certain other rights. Our licenses are irrevocable.
Licensors should read and understand the terms and conditions of the license
they choose before applying it. Licensors should also secure all rights necessary
before applying our licenses so that the public can reuse the material as
expected. Licensors should clearly mark any material not subject to the license.
This includes other CC-licensed material, or material used under an exception
or limitation to copyright. More considerations for licensors : wiki.creativecommons.org/Considerations_for_licensors
Considerations for the public: By using one of our public licenses, a licensor
grants the public permission to use the licensed material under specified
terms and conditions. If the licensor's permission is not necessary for any
reasonfor example, because of any applicable exception or limitation to copyrightthen
that use is not regulated by the license. Our licenses grant only permissions
under copyright and certain other rights that a licensor has authority to
grant. Use of the licensed material may still be restricted for other reasons,
including because others have copyright or other rights in the material. A
licensor may make special requests, such as asking that all changes be marked
or described. Although not required by our licenses, you are encouraged to
respect those requests where reasonable. More considerations for the public
: wiki.creativecommons.org/Considerations_for_licensees
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public
License
By exercising the Licensed Rights (defined below), You accept and agree to
be bound by the terms and conditions of this Creative Commons Attribution-NonCommercial-ShareAlike
4.0 International Public License ("Public License"). To the extent this Public
License may be interpreted as a contract, You are granted the Licensed Rights
in consideration of Your acceptance of these terms and conditions, and the
Licensor grants You such rights in consideration of benefits the Licensor
receives from making the Licensed Material available under these terms and
conditions.
Section 1 Definitions.
a. Adapted Material means material subject to Copyright and Similar Rights
that is derived from or based upon the Licensed Material and in which the
Licensed Material is translated, altered, arranged, transformed, or otherwise
modified in a manner requiring permission under the Copyright and Similar
Rights held by the Licensor. For purposes of this Public License, where the
Licensed Material is a musical work, performance, or sound recording, Adapted
Material is always produced where the Licensed Material is synched in timed
relation with a moving image.
b. Adapter's License means the license You apply to Your Copyright and Similar
Rights in Your contributions to Adapted Material in accordance with the terms
and conditions of this Public License.
c. BY-NC-SA Compatible License means a license listed at creativecommons.org/compatiblelicenses,
approved by Creative Commons as essentially the equivalent of this Public
License.
d. Copyright and Similar Rights means copyright and/or similar rights closely
related to copyright including, without limitation, performance, broadcast,
sound recording, and Sui Generis Database Rights, without regard to how the
rights are labeled or categorized. For purposes of this Public License, the
rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights.
e. Effective Technological Measures means those measures that, in the absence
of proper authority, may not be circumvented under laws fulfilling obligations
under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996,
and/or similar international agreements.
f. Exceptions and Limitations means fair use, fair dealing, and/or any other
exception or limitation to Copyright and Similar Rights that applies to Your
use of the Licensed Material.
g. License Elements means the license attributes listed in the name of a Creative
Commons Public License. The License Elements of this Public License are Attribution,
NonCommercial, and ShareAlike.
h. Licensed Material means the artistic or literary work, database, or other
material to which the Licensor applied this Public License.
i. Licensed Rights means the rights granted to You subject to the terms and
conditions of this Public License, which are limited to all Copyright and
Similar Rights that apply to Your use of the Licensed Material and that the
Licensor has authority to license.
j. Licensor means the individual(s) or entity(ies) granting rights under this
Public License.
k. NonCommercial means not primarily intended for or directed towards commercial
advantage or monetary compensation. For purposes of this Public License, the
exchange of the Licensed Material for other material subject to Copyright
and Similar Rights by digital file-sharing or similar means is NonCommercial
provided there is no payment of monetary compensation in connection with the
exchange.
l. Share means to provide material to the public by any means or process that
requires permission under the Licensed Rights, such as reproduction, public
display, public performance, distribution, dissemination, communication, or
importation, and to make material available to the public including in ways
that members of the public may access the material from a place and at a time
individually chosen by them.
m. Sui Generis Database Rights means rights other than copyright resulting
from Directive 96/9/EC of the European Parliament and of the Council of 11
March 1996 on the legal protection of databases, as amended and/or succeeded,
as well as other essentially equivalent rights anywhere in the world.
n. You means the individual or entity exercising the Licensed Rights under
this Public License. Your has a corresponding meaning.
Section 2 Scope.
a. License grant.
1. Subject to the terms and conditions of this Public License, the Licensor
hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive,
irrevocable license to exercise the Licensed Rights in the Licensed Material
to:
A. reproduce and Share the Licensed Material, in whole or in part, for NonCommercial
purposes only; and
B. produce, reproduce, and Share Adapted Material for NonCommercial purposes
only.
2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions
and Limitations apply to Your use, this Public License does not apply, and
You do not need to comply with its terms and conditions.
3. Term. The term of this Public License is specified in Section 6(a).
4. Media and formats; technical modifications allowed. The Licensor authorizes
You to exercise the Licensed Rights in all media and formats whether now known
or hereafter created, and to make technical modifications necessary to do
so. The Licensor waives and/or agrees not to assert any right or authority
to forbid You from making technical modifications necessary to exercise the
Licensed Rights, including technical modifications necessary to circumvent
Effective Technological Measures. For purposes of this Public License, simply
making modifications authorized by this Section 2(a)(4) never produces Adapted
Material.
5. Downstream recipients.
A. Offer from the Licensor Licensed Material. Every recipient of the Licensed
Material automatically receives an offer from the Licensor to exercise the
Licensed Rights under the terms and conditions of this Public License.
B. Additional offer from the Licensor Adapted Material. Every recipient
of Adapted Material from You automatically receives an offer from the Licensor
to exercise the Licensed Rights in the Adapted Material under the conditions
of the Adapter's License You apply.
C. No downstream restrictions. You may not offer or impose any additional
or different terms or conditions on, or apply any Effective Technological
Measures to, the Licensed Material if doing so restricts exercise of the Licensed
Rights by any recipient of the Licensed Material.
6. No endorsement. Nothing in this Public License constitutes or may be construed
as permission to assert or imply that You are, or that Your use of the Licensed
Material is, connected with, or sponsored, endorsed, or granted official status
by, the Licensor or others designated to receive attribution as provided in
Section 3(a)(1)(A)(i).
b. Other rights.
1. Moral rights, such as the right of integrity, are not licensed under this
Public License, nor are publicity, privacy, and/or other similar personality
rights; however, to the extent possible, the Licensor waives and/or agrees
not to assert any such rights held by the Licensor to the limited extent necessary
to allow You to exercise the Licensed Rights, but not otherwise.
2. Patent and trademark rights are not licensed under this Public License.
3. To the extent possible, the Licensor waives any right to collect royalties
from You for the exercise of the Licensed Rights, whether directly or through
a collecting society under any voluntary or waivable statutory or compulsory
licensing scheme. In all other cases the Licensor expressly reserves any right
to collect such royalties, including when the Licensed Material is used other
than for NonCommercial purposes.
Section 3 License Conditions.
Your exercise of the Licensed Rights is expressly made subject to the following
conditions.
a. Attribution.
1. If You Share the Licensed Material (including in modified form), You must:
A. retain the following if it is supplied by the Licensor with the Licensed
Material:
i. identification of the creator(s) of the Licensed Material and any others
designated to receive attribution, in any reasonable manner requested by the
Licensor (including by pseudonym if designated);
ii. a copyright notice;
iii. a notice that refers to this Public License;
iv. a notice that refers to the disclaimer of warranties;
v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable;
B. indicate if You modified the Licensed Material and retain an indication
of any previous modifications; and
C. indicate the Licensed Material is licensed under this Public License, and
include the text of, or the URI or hyperlink to, this Public License.
2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner
based on the medium, means, and context in which You Share the Licensed Material.
For example, it may be reasonable to satisfy the conditions by providing a
URI or hyperlink to a resource that includes the required information.
3. If requested by the Licensor, You must remove any of the information required
by Section 3(a)(1)(A) to the extent reasonably practicable.
b. ShareAlike.In addition to the conditions in Section 3(a), if You Share
Adapted Material You produce, the following conditions also apply.
1. The Adapter's License You apply must be a Creative Commons license with
the same License Elements, this version or later, or a BY-NC-SA Compatible
License.
2. You must include the text of, or the URI or hyperlink to, the Adapter's
License You apply. You may satisfy this condition in any reasonable manner
based on the medium, means, and context in which You Share Adapted Material.
3. You may not offer or impose any additional or different terms or conditions
on, or apply any Effective Technological Measures to, Adapted Material that
restrict exercise of the rights granted under the Adapter's License You apply.
Section 4 Sui Generis Database Rights.
Where the Licensed Rights include Sui Generis Database Rights that apply to
Your use of the Licensed Material:
a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract,
reuse, reproduce, and Share all or a substantial portion of the contents of
the database for NonCommercial purposes only;
b. if You include all or a substantial portion of the database contents in
a database in which You have Sui Generis Database Rights, then the database
in which You have Sui Generis Database Rights (but not its individual contents)
is Adapted Material, including for purposes of Section 3(b); and
c. You must comply with the conditions in Section 3(a) if You Share all or
a substantial portion of the contents of the database.
For the avoidance of doubt, this Section 4 supplements and does not replace
Your obligations under this Public License where the Licensed Rights include
other Copyright and Similar Rights.
Section 5 Disclaimer of Warranties and Limitation of Liability.
a. Unless otherwise separately undertaken by the Licensor, to the extent possible,
the Licensor offers the Licensed Material as-is and as-available, and makes
no representations or warranties of any kind concerning the Licensed Material,
whether express, implied, statutory, or other. This includes, without limitation,
warranties of title, merchantability, fitness for a particular purpose, non-infringement,
absence of latent or other defects, accuracy, or the presence or absence of
errors, whether or not known or discoverable. Where disclaimers of warranties
are not allowed in full or in part, this disclaimer may not apply to You.
b. To the extent possible, in no event will the Licensor be liable to You
on any legal theory (including, without limitation, negligence) or otherwise
for any direct, special, indirect, incidental, consequential, punitive, exemplary,
or other losses, costs, expenses, or damages arising out of this Public License
or use of the Licensed Material, even if the Licensor has been advised of
the possibility of such losses, costs, expenses, or damages. Where a limitation
of liability is not allowed in full or in part, this limitation may not apply
to You.
c. The disclaimer of warranties and limitation of liability provided above
shall be interpreted in a manner that, to the extent possible, most closely
approximates an absolute disclaimer and waiver of all liability.
Section 6 Term and Termination.
a. This Public License applies for the term of the Copyright and Similar Rights
licensed here. However, if You fail to comply with this Public License, then
Your rights under this Public License terminate automatically.
b. Where Your right to use the Licensed Material has terminated under Section
6(a), it reinstates:
1. automatically as of the date the violation is cured, provided it is cured
within 30 days of Your discovery of the violation; or
2. upon express reinstatement by the Licensor.
For the avoidance of doubt, this Section 6(b) does not affect any right the
Licensor may have to seek remedies for Your violations of this Public License.
c. For the avoidance of doubt, the Licensor may also offer the Licensed Material
under separate terms or conditions or stop distributing the Licensed Material
at any time; however, doing so will not terminate this Public License.
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License.
Section 7 Other Terms and Conditions.
a. The Licensor shall not be bound by any additional or different terms or
conditions communicated by You unless expressly agreed.
b. Any arrangements, understandings, or agreements regarding the Licensed
Material not stated herein are separate from and independent of the terms
and conditions of this Public License.
Section 8 Interpretation.
a. For the avoidance of doubt, this Public License does not, and shall not
be interpreted to, reduce, limit, restrict, or impose conditions on any use
of the Licensed Material that could lawfully be made without permission under
this Public License.
b. To the extent possible, if any provision of this Public License is deemed
unenforceable, it shall be automatically reformed to the minimum extent necessary
to make it enforceable. If the provision cannot be reformed, it shall be severed
from this Public License without affecting the enforceability of the remaining
terms and conditions.
c. No term or condition of this Public License will be waived and no failure
to comply consented to unless expressly agreed to by the Licensor.
d. Nothing in this Public License constitutes or may be interpreted as a limitation
upon, or waiver of, any privileges and immunities that apply to the Licensor
or You, including from the legal processes of any jurisdiction or authority.
Creative Commons is not a party to its public licenses. Notwithstanding, Creative
Commons may elect to apply one of its public licenses to material it publishes
and in those instances will be considered the "Licensor." The text of the
Creative Commons public licenses is dedicated to the public domain under the
CC0 Public Domain Dedication. Except for the limited purpose of indicating
that material is shared under a Creative Commons public license or as otherwise
permitted by the Creative Commons policies published at creativecommons.org/policies,
Creative Commons does not authorize the use of the trademark "Creative Commons"
or any other trademark or logo of Creative Commons without its prior written
consent including, without limitation, in connection with any unauthorized
modifications to any of its public licenses or any other arrangements, understandings,
or agreements concerning use of licensed material. For the avoidance of doubt,
this paragraph does not form part of the public licenses.
Creative Commons may be contacted at creativecommons.org.

View File

@ -1,5 +1,6 @@
const config = { const config = {
baseurl: 'https://dev.lauf-fuer-kaya.de', baseurl: 'https://dev.lauf-fuer-kaya.de',
fallback_username: 'admin', fallback_username: 'admin',
fallback_password: '72fpTzsev4xUu78QPs2FCbwZ3' fallback_password: '72fpTzsev4xUu78QPs2FCbwZ3',
prefersHashRouting: true
}; };

View File

@ -1,14 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="https://lauf-fuer-kaya.de/Bilder/kaya-logo-quadrat.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Lauf für Kaya! - Admin</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tailwindcss@2.0.2/dist/tailwind.min.css">
</head>
<body>
<script src="/env.js"></script>
<script defer type="module" src="/src/index.js"></script>
</body>
</html>

23
index.template.html Normal file
View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.png" />
<link rel="manifest" href="/manifest.webmanifest">
<link rel="apple-touch-icon" href="/lfk-logo.png">
<meta name="theme-color" content="#FFFFFF">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Lauf Für Kaya! - Admin" />
<title>Lauf für Kaya! - Admin</title>
__TAILWIND_INSERT__
</head>
<body>
<span style="display: none;visibility: hidden;" id="buildinfo">RELEASE_INFO-0.6.0-RELEASE_INFO</span>
<noscript>You need to enable JavaScript to run this app.</noscript>
<script src="/env.js"></script>
<script defer type="module" src="/_dist_/index.js"></script>
</body>
</html>

View File

@ -1,19 +1,24 @@
events{} events {
}
http { http {
include mime.types; include mime.types;
sendfile on; sendfile on;
server { server {
error_page 404 /index.html;
root /usr/share/nginx/html; root /usr/share/nginx/html;
location ~* \.(?:ico|css|js|gif|jpe?g|png)$ { location / {
try_files $uri $uri/ /index.html;
}
location ~* \.(?:ico|css|gif|jpe?g|png)$ {
expires 1y; expires 1y;
add_header Pragma public; add_header Pragma public;
add_header Cache-Control "public"; add_header Cache-Control "public";
} }
# --- Brotli # --- Brotli
# brotli on; brotli on;
# brotli_comp_level 6; brotli_comp_level 6;
# brotli_static on; brotli_static on;
# brotli_types application/atom+xml application/javascript application/json application/rss+xml application/vnd.ms-fontobject application/x-font-opentype application/x-font-truetype application/x-font-ttf application/x-javascript application/xhtml+xml application/xml font/eot font/opentype font/otf font/truetype image/svg+xml image/vnd.microsoft.icon image/x-icon image/x-win-bitmap text/css text/javascript text/plain text/xml; brotli_types application/atom+xml application/javascript application/json application/rss+xml application/vnd.ms-fontobject application/x-font-opentype application/x-font-truetype application/x-font-ttf application/x-javascript application/xhtml+xml application/xml font/eot font/opentype font/otf font/truetype image/svg+xml image/vnd.microsoft.icon image/x-icon image/x-win-bitmap text/css text/javascript text/plain text/xml;
# --- GZIP # --- GZIP
gzip on; gzip on;
gzip_disable "msie6"; gzip_disable "msie6";
@ -22,6 +27,25 @@ http {
gzip_comp_level 6; gzip_comp_level 6;
gzip_buffers 16 8k; gzip_buffers 16 8k;
gzip_http_version 1.1; gzip_http_version 1.1;
gzip_types application/javascript application/rss+xml application/vnd.ms-fontobject application/x-font application/x-font-opentype application/x-font-otf application/x-font-truetype application/x-font-ttf application/x-javascript application/xhtml+xml application/xml font/opentype font/otf font/ttf image/svg+xml image/x-icon text/css text/javascript text/plain text/xml; gzip_types application/javascript
application/rss+xml
application/vnd.ms-fontobject
application/x-font
application/x-font-opentype
application/x-font-otf
application/x-font-truetype
application/x-font-ttf
application/x-javascript
application/xhtml+xml
application/xml
font/opentype
font/otf
font/ttf
image/svg+xml
image/x-icon
text/css
text/javascript
text/plain
text/xml;
} }
} }

16
order.js Normal file
View File

@ -0,0 +1,16 @@
const fs = require('fs');
// get all language files
const files = fs.readdirSync('./src/locales/');
files.forEach((f) => {
// read file as object
const unordered = JSON.parse(fs.readFileSync(`src/locales/${f}`));
// order object by keys alpabetically A-Z
const ordered = Object.keys(unordered).sort().reduce((obj, key) => {
obj[key] = unordered[key];
return obj;
}, {});
// format output as json for commit diff compatibility
const out = JSON.stringify(ordered, 0, 4);
// write output file
fs.writeFileSync(`src/locales/${f}`, out);
});

View File

@ -1,21 +1,62 @@
{ {
"name": "@odit/lfk-frontend", "name": "@odit/lfk-frontend",
"version": "0.0.0", "version": "0.6.0",
"scripts": { "scripts": {
"dev": "svite", "i18n-order": "node order.js",
"build": "svite build", "dev:all": "yarn prebuild && snowpack dev",
"start": "svite serve" "dev": "cross-env NODE_ENV_ODIT=development_fast node template-copy.js && yarn build:sw && snowpack dev",
"build": "yarn prebuild && snowpack build",
"prebuild": "cross-env NODE_ENV_ODIT=production node template-copy.js && yarn build:sw",
"build:sw": "workbox generateSW workbox-config.js",
"release": "release-it",
"licenses:export": "license-exporter --json -o public"
}, },
"license": "CC-BY-NC-SA-4.0",
"dependencies": { "dependencies": {
"@odit/lfk-client-js": "^0.0.4", "@odit/lfk-client-js": "0.4.5",
"svelte-i18n": "^3.3.0", "csvtojson": "^2.0.10",
"svelte-spa-router": "^3.1.0", "gridjs": "3.3.0",
"toastify-js": "^1.9.3", "localforage": "1.9.0",
"validator": "^13.5.2" "lodash.isequal": "^4.5.0",
"marked": "^2.0.0",
"svelte-focus-trap": "1.0.1",
"svelte-i18n": "3.3.2",
"svelte-select": "^3.16.1",
"tailwindcss": "2.0.3",
"tinro": "0.5.12",
"toastify-js": "1.9.3",
"validator": "13.5.2",
"xlsx": "^0.16.9"
}, },
"devDependencies": { "devDependencies": {
"svelte": "^3.31.0", "@odit/license-exporter": "^0.0.10",
"svelte-hmr": "^0.12.2", "@snowpack/plugin-svelte": "3.5.2",
"svite": "^0.8.1" "auto-changelog": "^2.2.1",
"autoprefixer": "10.2.4",
"cross-env": "^7.0.3",
"postcss": "8.2.6",
"postcss-load-config": "3.0.1",
"release-it": "^14.4.0",
"snowpack": "3.0.11",
"svelte": "3.32.3",
"svelte-preprocess": "4.6.8",
"workbox-cli": "6.1.0"
},
"release-it": {
"git": {
"commit": true,
"requireCleanWorkingDir": false,
"commitMessage": "🚀RELEASE v${version}",
"push": false,
"tag": true,
"tagName": null,
"tagAnnotation": "v${version}"
},
"npm": {
"publish": false
},
"hooks": {
"after:bump": "npx auto-changelog --commit-limit false -p -u --hide-credit && git add CHANGELOG.md && node versionbuilder.js && git add index.template.html && node order.js && git add src/locales"
}
} }
} }

View File

@ -1,6 +1,5 @@
const config = { const config = {
baseurl: 'http://localhost:4010', baseurl: 'http://localhost:4010',
// optional params ⏬ // optional
fallback_username: 'demo', prefersHashRouting: true
fallback_password: 'demo'
}; };

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
public/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

1
public/imprint_en.md Normal file
View File

@ -0,0 +1 @@
Nostrud tempor dolor aute ea excepteur aute mollit elit eiusmod exercitation. Magna laborum pariatur adipisicing pariatur cupidatat exercitation duis aliquip pariatur sint exercitation deserunt labore. Consectetur id laboris dolore nostrud do velit ipsum. Eu laboris velit do commodo ad ea sint ex cillum. Cillum ipsum qui eiusmod laborum mollit sunt dolore incididunt. Cillum sunt culpa veniam voluptate et qui ut magna anim occaecat ut mollit dolor. Duis irure proident eu incididunt dolore sunt nisi aute dolore amet eu fugiat laboris quis.

BIN
public/lfk-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

1
public/licenses.json Normal file

File diff suppressed because one or more lines are too long

4
public/logo.svg Normal file
View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 98.1 118">
<path fill="#ff3e00" d="M91.8 15.6C80.9-.1 59.2-4.7 43.6 5.2L16.1 22.8A31.25 31.25 0 001.9 43.9c-1.3 7.3-.2 14.8 3.3 21.3-2.4 3.6-4 7.6-4.7 11.8-1.6 8.9.5 18.1 5.7 25.4 11 15.7 32.6 20.3 48.2 10.4l27.5-17.5c7.5-4.7 12.7-12.4 14.2-21.1 1.3-7.3.2-14.8-3.3-21.3 2.4-3.6 4-7.6 4.7-11.8 1.7-9-.4-18.2-5.7-25.5"/>
<path fill="#fff" d="M40.9 103.9a21.8 21.8 0 01-23.4-8.7c-3.2-4.4-4.4-9.9-3.5-15.3l.6-2.6.5-1.6 1.4 1c3.3 2.4 6.9 4.2 10.8 5.4l1 .3-.1 1c-.1 1.4.3 2.9 1.1 4.1a6.62 6.62 0 008.8 2L65.5 72c1.4-.9 2.3-2.2 2.6-3.8.3-1.6-.1-3.3-1-4.6a6.56 6.56 0 00-8.8-1.9l-10.5 6.7a18.6 18.6 0 01-5.6 2.4 21.8 21.8 0 01-23.4-8.7 20.2 20.2 0 01-3.4-15.3c.9-5.2 4.1-9.9 8.6-12.7l27.5-17.5c1.7-1.1 3.6-1.9 5.6-2.5a21.8 21.8 0 0123.4 8.7c3.2 4.4 4.4 9.9 3.5 15.3-.2.9-.4 1.7-.7 2.6l-.5 1.6-1.4-1c-3.3-2.4-6.9-4.2-10.8-5.4l-1-.3.1-1c.1-1.4-.3-2.9-1.1-4.1a6.56 6.56 0 00-8.8-1.9L32.4 46.1c-1.4.9-2.3 2.2-2.6 3.8s.1 3.3 1 4.6a6.56 6.56 0 008.8 1.9l10.5-6.7c1.7-1.1 3.6-1.9 5.6-2.5a21.8 21.8 0 0123.4 8.7c3.2 4.4 4.4 9.9 3.5 15.3-.9 5.2-4.1 9.9-8.6 12.7l-27.5 17.5c-1.7 1.1-3.6 1.9-5.6 2.5"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,34 @@
{
"name": "Lauf für Kaya! - Admin",
"short_name": "LfK!Admin",
"start_url": "/?utm_source=pwa",
"orientation": "portrait-primary",
"display": "standalone",
"background_color": "#fff",
"theme_color": "#fff",
"description": "Lauf für Kaya! - Admin",
"shortcuts": [
{
"name": "Users",
"url": "/users/?utm_source=pwa"
}
],
"icons": [
{
"src": "/favicon.png",
"sizes": "48x48",
"type": "image/png"
},
{
"src": "/favicon.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "/lfk-logo.png",
"sizes": "1540x144",
"type": "image/png"
},
{ "src": "/maskable_icon_x1.png", "sizes": "750x750", "type": "image/png", "purpose": "any maskable" }
]
}

BIN
public/maskable_icon_x1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

1
public/privacy_en.md Normal file
View File

@ -0,0 +1 @@
Nostrud tempor dolor aute ea excepteur aute mollit elit eiusmod exercitation. Magna laborum pariatur adipisicing pariatur cupidatat exercitation duis aliquip pariatur sint exercitation deserunt labore. Consectetur id laboris dolore nostrud do velit ipsum. Eu laboris velit do commodo ad ea sint ex cillum. Cillum ipsum qui eiusmod laborum mollit sunt dolore incididunt. Cillum sunt culpa veniam voluptate et qui ut magna anim occaecat ut mollit dolor. Duis irure proident eu incididunt dolore sunt nisi aute dolore amet eu fugiat laboris quis.

3
public/robots.txt Normal file
View File

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

6
s-config.template.js Normal file
View File

@ -0,0 +1,6 @@
const sveltePreprocess = require('svelte-preprocess');
const preprocess = sveltePreprocess(__insert__);
module.exports = {
preprocess
};

26
snowpack.config.js Normal file
View File

@ -0,0 +1,26 @@
/** @type {import("snowpack").SnowpackUserConfig } */
module.exports = {
mount: {
public: '/',
src: '/_dist_'
},
plugins: [ '@snowpack/plugin-svelte' ],
routes: [
/* Enable an SPA Fallback in development: */
{ match: 'routes', src: '.*', dest: '/index.html' }
],
packageOptions: {
/* ... */
sourceMap: false
},
devOptions: {
/* ... */
},
buildOptions: {
/* ... */
},
alias: {
/* ... */
},
optimize: { bundle: true, minify: true }
};

View File

@ -1,54 +1,152 @@
<script> <script>
import Router from "svelte-spa-router"; import "./TailwindStyles.svelte";
import { replace } from "svelte-spa-router"; import "toastify-js/src/toastify.css";
import { wrap } from "svelte-spa-router/wrap"; import "gridjs/dist/theme/mermaid.css";
import { Route, router } from "tinro";
router.subscribe((routeInfo) => {
window.scrollTo(0, 0);
});
if (config.prefersHashRouting) {
if (config.prefersHashRouting === true) {
router.useHashNavigation();
}
}
import localForage from "localforage";
import { addMessages, init, getLocaleFromNavigator } from "svelte-i18n"; import { addMessages, init, getLocaleFromNavigator } from "svelte-i18n";
import en from "./locales/en"; import en from "./locales/en.json";
import de from "./locales/de"; import de from "./locales/de.json";
addMessages("en", en); addMessages("en", en);
addMessages("de", de); addMessages("de", de);
init({ init({
fallbackLocale: "en", fallbackLocale: "en",
initialLocale: getLocaleFromNavigator(), initialLocale: getLocaleFromNavigator(),
}); });
localForage.config({
name: "lfk_admin",
version: 1.0,
storeName: "lfk_admin",
description: "LfK! admin dashbaord",
});
// //
import Login from "./components/Login.svelte"; import Login from "./components/auth/Login.svelte";
import Dashboard from "./components/Dashboard.svelte"; import Dashboard from "./components/dashboard/Dashboard.svelte";
import Footer from "./components/Footer.svelte";
import NotFound from "./components/NotFound.svelte";
import Tracks from "./components/Tracks.svelte";
// import Profile from "./components/Profile.svelte";
import store from "./store.js"; import store from "./store.js";
import ForgotPassword from "./components/ForgotPassword.svelte"; import ForgotPassword from "./components/auth/ForgotPassword.svelte";
import MainDashContent from "./components/dashboard/MainDashContent.svelte";
import Users from "./components/users/Users.svelte";
import About from "./components/general/About.svelte";
import Settings from "./components/general/Settings.svelte";
import Transition from "./components/base/Transition.svelte";
import Orgs from "./components/orgs/Orgs.svelte";
import Runners from "./components/runners/Runners.svelte";
import Footer from "./components/general/Footer.svelte";
import TracksOverview from "./components/tracks/TracksOverview.svelte";
import OrgDetail from "./components/orgs/OrgDetail.svelte";
import Teams from "./components/teams/Teams.svelte";
import { OpenAPI } from "@odit/lfk-client-js";
import UserDetail from "./components/users/UserDetail.svelte";
OpenAPI.BASE = config.baseurl;
import { register as registerSW } from "./swmodule";
import TeamDetail from "./components/teams/TeamDetail.svelte";
import UserPermissions from "./components/users/UserPermissions.svelte";
import RunnerDetail from "./components/runners/RunnerDetail.svelte";
import Imprint from "./components/general/Imprint.svelte";
import Privacy from "./components/general/Privacy.svelte";
import ResetPassword from "./components/auth/ResetPassword.svelte";
import Contacts from "./components/contacts/Contacts.svelte";
import ContactDetail from "./components/contacts/ContactDetail.svelte";
store.init(); store.init();
$: logged_in = $store.isLoggedIn; registerSW();
//
const checkAuth = (detail) => {
if (!$store.isLoggedIn) {
console.log("not authed");
replace("/login/");
return false;
}
return true;
};
const routes = {
"/login": Login,
"/forgot_password": ForgotPassword,
"/dashboard": wrap({ component: Dashboard, conditions: [checkAuth] }),
"/": wrap({ component: Dashboard, conditions: [checkAuth] }),
// "/password_reset_sent/*": Book,
// Using named parameters, with last being optional
// "/author/:first/:last?": Author,
// Wildcard parameter
// "/book/*": Book,
// Catch-all
// This is optional, but if present it must be the last
"*": NotFound,
};
//
</script> </script>
<Router {routes} /> <Route>
{#if $router.path === '/forgot_password'}
<Route path="/forgot_password">
<ForgotPassword />
</Route>
{:else if $router.path.includes('/reset')}
<Route path="/reset/:resetkey" let:params>
<ResetPassword {params} />
</Route>
{:else if $router.path === '/about'}
<Route path="/about">
<About />
</Route>
{:else if $router.path === '/imprint'}
<Route path="/imprint">
<Imprint />
</Route>
{:else if $router.path === '/privacy'}
<Route path="/privacy">
<Privacy />
</Route>
{:else if $store.isLoggedIn}
<Dashboard>
<Transition>
<Route path="/">
<MainDashContent />
</Route>
<Route path="/users/*">
<Route path="/">
<Users />
</Route>
<Route path="/:userid/*" let:params>
<Route path="/">
<UserDetail {params} />
</Route>
<Route path="/permissions/">
<UserPermissions {params} />
</Route>
</Route>
</Route>
<Route path="/tracks/*">
<Route path="/">
<TracksOverview />
</Route>
<Route path="/:trackid" let:params />
</Route>
<Route path="/runners/*">
<Route path="/">
<Runners />
</Route>
<Route path="/:runnerid" let:params>
<RunnerDetail {params} />
</Route>
</Route>
<Route path="/teams/*">
<Route path="/">
<Teams />
</Route>
<Route path="/:teamid" let:params>
<TeamDetail {params} />
</Route>
</Route>
<Route path="/contacts/*">
<Route path="/">
<Contacts />
</Route>
<Route path="/:contact" let:params>
<ContactDetail {params} />
</Route>
</Route>
<Route path="/orgs/*">
<Route path="/">
<Orgs />
</Route>
<Route path="/:orgid" let:params>
<OrgDetail {params} />
</Route>
</Route>
<Route path="/about">
<About />
</Route>
<Route path="/settings">
<Settings />
</Route>
</Transition>
<Footer />
</Dashboard>
{:else}
<Login />
{/if}
</Route>

View File

@ -0,0 +1,6 @@
<style global>
/*! @import */
@tailwind base;
@tailwind components;
@tailwind utilities;
</style>

File diff suppressed because it is too large Load Diff

View File

@ -1,354 +0,0 @@
<script>
import { _ } from "svelte-i18n";
import StatCards from "./StatCards.svelte";
import Table from "./Table.svelte";
import Pagination from "./Pagination.svelte";
import FormLayout from "./FormLayout.svelte";
import Tracks from "./Tracks.svelte";
import BreadcrumbNav from "./BreadcrumbNav.svelte";
import NotFound from "./NotFound.svelte";
//
let isProfileActionsVisible = false;
let mobile_menu_opened = false;
</script>
<div>
<nav class="bg-gray-800">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex items-center justify-between h-16">
<div class="flex items-center">
<div class="flex-shrink-0">
<img
class="h-8 w-8"
src="https://lauf-fuer-kaya.de/Bilder/kaya-logo-quadrat.png"
alt="Workflow" />
</div>
<span
class="text-white px-3 py-2 text-lg font-medium">{$_('application_name')}</span>
<div class="hidden md:block">
<div class="ml-10 flex items-baseline space-x-4">
<a
href="#"
class="bg-gray-900 text-white px-3 py-2 rounded-md text-sm font-medium">Dashboard</a>
<a
href="#"
class="text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium">Team</a>
<a
href="#"
class="text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium">Projects</a>
<a
href="#"
class="text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium">Calendar</a>
<a
href="#"
class="text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium">Reports</a>
<form class="w-full max-w-xs mr-2 navbar-search">
<div class="relative">
<input
type="search"
name="search"
placeholder="Search..."
class="pl-10 pr-5 appearance-none h-10 w-full rounded-full text-sm focus:outline-none" /><button
type="submit"
class="absolute top-0 mt-3 left-0 ml-4"><svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="stroke-current h-4 w-4"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"><circle
cx="11"
cy="11"
r="8" />
<line
x1="21"
y1="21"
x2="16.65"
y2="16.65" /></svg></button>
</div>
</form>
</div>
</div>
</div>
<div class="hidden md:block">
<div class="ml-4 flex items-center md:ml-6">
<button
class="bg-gray-800 p-1 rounded-full text-gray-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-white">
<span class="sr-only">View notifications</span>
<!-- Heroicon name: bell -->
<svg
class="h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
</svg>
</button>
<!-- Profile dropdown -->
<div class="ml-3 relative">
<div>
<button
on:click={() => {
isProfileActionsVisible = !isProfileActionsVisible;
}}
class="max-w-xs bg-gray-800 rounded-full flex items-center text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-white"
id="user-menu"
aria-haspopup="true">
<span class="sr-only">Open user menu</span>
<img
class="h-8 w-8 rounded-full"
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
alt="" />
</button>
</div>
<!--
Profile dropdown panel, show/hide based on dropdown state.
Entering: "transition ease-out duration-100"
From: "transform opacity-0 scale-95"
To: "transform opacity-100 scale-100"
Leaving: "transition ease-in duration-75"
From: "transform opacity-100 scale-100"
To: "transform opacity-0 scale-95"
-->
{#if isProfileActionsVisible}
<!-- content here -->
<div
class="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5"
role="menu"
aria-orientation="vertical"
aria-labelledby="user-menu">
<a
href="/profile"
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
role="menuitem">{$_('your_profile')}</a>
<a
href="/profile/settings"
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
role="menuitem">{$_('settings')}</a>
<a
href="/logout"
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
role="menuitem">{$_('signout')}</a>
</div>
{/if}
</div>
</div>
</div>
<div class="-mr-2 flex md:hidden">
<!-- Mobile menu button -->
<button
on:click={() => {
mobile_menu_opened = !mobile_menu_opened;
}}
class="bg-gray-800 inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-white">
<span class="sr-only">Open main menu</span>
<!--
Heroicon name: menu
Menu open: "hidden", Menu closed: "block"
-->
<svg
class="block h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h16M4 18h16" />
</svg>
<!--
Heroicon name: x
Menu open: "block", Menu closed: "hidden"
-->
<svg
class="hidden h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
</div>
<!--
Mobile menu, toggle classes based on menu state.
Open: "block", closed: "hidden"
-->
<div
class="md:hidden"
class:block={mobile_menu_opened === true}
class:hidden={mobile_menu_opened === false}>
<div class="px-2 pt-2 pb-3 space-y-1 sm:px-3">
<a
href="/"
class="bg-gray-900 text-white block px-3 py-2 rounded-md text-base font-medium">Dashboard</a>
<a
href="#"
class="text-gray-300 hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium">Team</a>
<a
href="#"
class="text-gray-300 hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium">Projects</a>
<a
href="#"
class="text-gray-300 hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium">Calendar</a>
<a
href="#"
class="text-gray-300 hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium">Reports</a>
</div>
<div class="pt-4 pb-3 border-t border-gray-700">
<div class="flex items-center px-5">
<div class="flex-shrink-0">
<img
class="h-10 w-10 rounded-full"
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
alt="" />
</div>
<div class="ml-3">
<div class="text-base font-medium leading-none text-white">
Tom Cook
</div>
<div class="text-sm font-medium leading-none text-gray-400">
tom@example.com
</div>
</div>
<button
class="ml-auto bg-gray-800 flex-shrink-0 p-1 rounded-full text-gray-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-white">
<span class="sr-only">View notifications</span>
<!-- Heroicon name: bell -->
<svg
class="h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
</svg>
</button>
</div>
<div class="mt-3 px-2 space-y-1">
<a
href="#"
class="block px-3 py-2 rounded-md text-base font-medium text-gray-400 hover:text-white hover:bg-gray-700">{$_('your_profile')}</a>
<a
href="#"
class="block px-3 py-2 rounded-md text-base font-medium text-gray-400 hover:text-white hover:bg-gray-700">{$_('settings')}</a>
<a
href="#"
class="block px-3 py-2 rounded-md text-base font-medium text-gray-400 hover:text-white hover:bg-gray-700">{$_('signout')}</a>
</div>
</div>
</div>
</nav>
<!-- <header class="bg-white shadow">
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
<h1 class="text-3xl leading-tight">
<span class="font-bold">Dashboard</span><span> - hello there 👋</span>
</h1>
</div>
</header> -->
<div class="shadow px-6 pt-4 pb-1">
<BreadcrumbNav />
</div>
<main>
<div class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
<StatCards />
<div class="mb-8">
<Tracks />
</div>
<Pagination />
<div class="mb-8">
<Table />
</div>
<!-- -->
<div
class="widget w-full p-4 mb-4 rounded-lg bg-white border border-grey-100 dark:bg-grey-895 dark:border-grey-890">
<div class="flex flex-row items-center justify-between mb-6">
<div class="flex flex-col">
<div class="text-sm font-light text-grey-500">Regular</div>
<div class="text-sm font-bold"><span>Text inputs</span></div>
</div>
</div>
<div class="flex flex-col lg:flex-row lg:flex-wrap w-full lg:space-x-4">
<div class="w-full lg:w-1/4">
<div class="form-element ">
<div class="form-label">Label</div><input
name="name"
type="text"
class="form-input"
placeholder="Enter something..." />
<div class="form-hint">This is a hint</div>
</div>
</div>
<div class="w-full lg:w-1/4">
<div class="form-element ">
<div class="form-label">First name</div><input
name="name"
type="text"
class="form-input form-input-invalid"
placeholder="john@example.com" />
<div class="form-error">First name is required</div>
</div>
</div>
<div class="w-full lg:w-1/4">
<div class="form-element ">
<div class="form-label">First name</div><input
name="name"
type="text"
class="form-input form-input-valid"
placeholder="john@example.com" />
<div class="form-success">First name is valid</div>
</div>
</div>
</div>
</div>
<!-- -->
<div class="mb-8">
<FormLayout />
</div>
<NotFound />
</div>
</main>
</div>

View File

@ -1,13 +0,0 @@
<script>
import { _ } from "svelte-i18n";
</script>
<footer class="text-gray-700 body-font">
<div
class="container px-5 py-8 mx-auto flex items-center sm:flex-row flex-col">
<p
class="text-sm text-gray-500 sm:ml-4 sm:pl-4 sm:py-2 sm:mt-0 mt-4">
proudly powered by <a class="underline" href="https://odit.services" rel="noopener,noreferrer" target="_blank">ODIT.Services</a>
</p>
</div>
</footer>

View File

@ -1,97 +0,0 @@
<script>
import store from "../store.js";
store.init();
const login = () => {
store.login();
};
</script>
<div
class="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
<div class="max-w-md w-full space-y-8">
<div>
<img
class="mx-auto h-12 w-auto"
src="https://lauf-fuer-kaya.de/Bilder/kaya-logo-quadrat.png"
alt="" />
<h2 class="mt-6 text-center text-3xl font-extrabold text-gray-900">
Sign in to your account
</h2>
<p class="mt-2 text-center text-sm text-gray-600">
Or
<a href="#" class="font-medium text-indigo-600 hover:text-indigo-500">
start your 14-day free trial
</a>
</p>
</div>
<div>
<input type="hidden" name="remember" value="true" />
<div class="rounded-md shadow-sm -space-y-px">
<div>
<label for="email-address" class="sr-only">Email address</label>
<input
id="email-address"
name="email"
type="email"
autocomplete="email"
required
class="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
placeholder="Email address" />
</div>
<div>
<label for="password" class="sr-only">Password</label>
<input
id="password"
name="password"
type="password"
autocomplete="current-password"
required
class="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
placeholder="Password" />
</div>
</div>
<div class="flex items-center justify-between">
<div class="flex items-center">
<input
id="remember_me"
name="remember_me"
type="checkbox"
class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded" />
<label for="remember_me" class="ml-2 block text-sm text-gray-900">
Remember me
</label>
</div>
<div class="text-sm">
<a href="#" class="font-medium text-indigo-600 hover:text-indigo-500">
Forgot your password?
</a>
</div>
</div>
<div>
<button
on:click="{login}"
type="submit"
class="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
<span class="absolute left-0 inset-y-0 flex items-center pl-3">
<!-- Heroicon name: lock-closed -->
<svg
class="h-5 w-5 text-indigo-500 group-hover:text-indigo-400"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true">
<path
fill-rule="evenodd"
d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z"
clip-rule="evenodd" />
</svg>
</span>
Sign in
</button>
</div>
</div>
</div>
</div>

View File

@ -1,96 +0,0 @@
<script>
import { _ } from "svelte-i18n";
</script>
<div
class="flex flex-col lg:flex-row w-full lg:space-x-2 space-y-2 lg:space-y-0 mb-2 lg:mb-4">
<div class="w-full lg:w-1/4">
<div
class="widget w-full p-4 rounded-lg bg-white border border-grey-100 dark:bg-grey-895 dark:border-grey-890">
<div class="flex flex-row items-center justify-between">
<div class="flex flex-col">
<div class="text-xs uppercase font-light text-grey-500">
{$_('runners')}
</div>
<div class="text-xl font-bold">69</div>
</div><svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
size="24"
class="stroke-current text-grey-500"
height="24"
width="24"
xmlns="http://www.w3.org/2000/svg"><path
d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
<circle cx="9" cy="7" r="4" />
<path d="M23 21v-2a4 4 0 0 0-3-3.87" />
<path d="M16 3.13a4 4 0 0 1 0 7.75" /></svg>
</div>
</div>
</div>
<div class="w-full lg:w-1/4">
<div
class="widget w-full p-4 rounded-lg bg-white border border-grey-100 dark:bg-grey-895 dark:border-grey-890">
<div class="flex flex-row items-center justify-between">
<div class="flex flex-col">
<div class="text-xs uppercase font-light text-grey-500">
{$_('total-scans')}
</div>
<div class="text-xl font-bold">1500</div>
</div><svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
size="24"
class="stroke-current text-grey-500"
height="24"
width="24"
xmlns="http://www.w3.org/2000/svg"><polyline
points="22 12 18 12 15 21 9 3 6 12 2 12" /></svg>
</div>
</div>
</div>
<div class="w-full lg:w-1/4">
<div
class="widget w-full p-4 rounded-lg bg-white border border-grey-100 dark:bg-grey-895 dark:border-grey-890">
<div class="flex flex-row items-center justify-between">
<div class="flex flex-col">
<div class="text-xs uppercase font-light text-grey-500">
{$_('total-donations')}
</div>
<div class="text-xl font-bold">5000,00 €</div>
</div><svg
xmlns="http://www.w3.org/2000/svg"
height="24"
width="24"><path d="M0 0h24v24H0z" fill="none" />
<path
d="M15 18.5A6.48 6.48 0 019.24 15H15v-2H8.58c-.05-.33-.08-.66-.08-1s.03-.67.08-1H15V9H9.24A6.491 6.491 0 0115 5.5c1.61 0 3.09.59 4.23 1.57L21 5.3A8.955 8.955 0 0015 3c-3.92 0-7.24 2.51-8.48 6H3v2h3.06a8.262 8.262 0 000 2H3v2h3.52c1.24 3.49 4.56 6 8.48 6 2.31 0 4.41-.87 6-2.3l-1.78-1.77c-1.13.98-2.6 1.57-4.22 1.57z" /></svg>
</div>
</div>
</div>
<div class="w-full lg:w-1/4">
<div
class="widget w-full p-4 rounded-lg bg-white border border-grey-100 dark:bg-grey-895 dark:border-grey-890">
<div class="flex flex-row items-center justify-between">
<div class="flex flex-col">
<div class="text-xs uppercase font-light text-grey-500">
{$_('total-distance')}
</div>
<div class="text-xl font-bold">100.0 km</div>
</div>
<svg xmlns="http://www.w3.org/2000/svg" height="24" width="24"><path
d="M0 0h24v24H0z"
fill="none" />
<path
d="M21 6H3c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 10H3V8h2v4h2V8h2v4h2V8h2v4h2V8h2v4h2V8h2v8z" /></svg>
</div>
</div>
</div>
</div>

View File

@ -1,5 +0,0 @@
<script>
import { _, locale } from "svelte-i18n";
</script>
<div>$locale $_('hallo')</div>

View File

@ -1,34 +0,0 @@
<script>
import { _ } from "svelte-i18n";
import store from "../store.js";
import { TrackService } from "@odit/lfk-client-js";
let tracks_promise = TrackService.trackControllerGetAll();
</script>
<h1>{$_('tracks')}</h1>
{#await tracks_promise}
<div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert">
<p class="font-bold">track data is being loaded...</p>
<p class="text-sm">This might take a moment 👀</p>
</div>
{:then tracks}
{#if tracks.length > 0}
<h4>{tracks.length}</h4>
<hr />
<ul>
{#each tracks as item}
<li>{item.name}</li>
<li>{item.distance}</li>
{/each}
</ul>
{:else}keine Tracks{/if}
{:catch error}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<span class="inline-block align-middle mr-8">
<b class="capitalize">😢 Error</b>
{error}
</span>
</div>
{/await}

View File

@ -1,27 +1,27 @@
<script> <script>
import { ApiError, AuthService } from "@odit/lfk-client-js";
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import store from "../store.js";
store.init();
import { push, pop, replace } from "svelte-spa-router";
//
import { OpenAPI, AuthService } from "@odit/lfk-client-js";
import isEmail from "validator/es/lib/isEmail";
//
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import "toastify-js/src/toastify.css"; import "toastify-js/src/toastify.css";
import isEmail from "validator/es/lib/isEmail";
let reset_mail_sent = false; let reset_mail_sent = false;
let usersEmail = ""; let usersEmail = "";
function reset() { function reset() {
if (isEmail(usersEmail)) { if (isEmail(usersEmail)) {
Toastify({ AuthService.authControllerGetResetToken({ email: usersEmail })
text: $_("mail-validation-in-progress"), .then((resp) => {
duration: 3500, Toastify({
}).showToast(); text: $_("mail-validation-in-progress"),
reset_mail_sent = true; duration: 3500,
}).showToast();
reset_mail_sent = true;
})
.catch((err) => {
});
} else { } else {
Toastify({ Toastify({
text: $_('invalid-mail-reset'), text: $_("invalid-mail-reset"),
duration: 3500, duration: 3500,
}).showToast(); }).showToast();
} }
@ -31,11 +31,7 @@
{#if reset_mail_sent} {#if reset_mail_sent}
<div class="min-h-screen flex items-center justify-center bg-gray-100"> <div class="min-h-screen flex items-center justify-center bg-gray-100">
<div class="max-w-md w-full py-12 px-6"> <div class="max-w-md w-full py-12 px-6">
<img <img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" />
style="height:10rem;"
class="mx-auto"
src="https://lauf-fuer-kaya.de/Bilder/kaya-logo-quadrat.png"
alt="" />
<p class="mt-6 text-lg text-center font-bold text-gray-900"> <p class="mt-6 text-lg text-center font-bold text-gray-900">
{$_('application_name')} {$_('application_name')}
</p> </p>
@ -47,7 +43,7 @@
<div class="mt-6"> <div class="mt-6">
<div class="mt-6"> <div class="mt-6">
<a <a
href="/#/" href="/"
class="block w-full text-center py-2 px-3 border border-gray-300 rounded-md text-gray-900 font-medium hover:border-gray-400 focus:outline-none focus:border-gray-400 sm:text-sm"> class="block w-full text-center py-2 px-3 border border-gray-300 rounded-md text-gray-900 font-medium hover:border-gray-400 focus:outline-none focus:border-gray-400 sm:text-sm">
{$_('goback')} {$_('goback')}
</a> </a>
@ -58,11 +54,7 @@
{:else} {:else}
<div class="min-h-screen flex items-center justify-center bg-gray-100"> <div class="min-h-screen flex items-center justify-center bg-gray-100">
<div class="max-w-md w-full py-12 px-6"> <div class="max-w-md w-full py-12 px-6">
<img <img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" />
style="height:10rem;"
class="mx-auto"
src="https://lauf-fuer-kaya.de/Bilder/kaya-logo-quadrat.png"
alt="" />
<p class="mt-6 text-lg text-center font-bold text-gray-900"> <p class="mt-6 text-lg text-center font-bold text-gray-900">
{$_('application_name')} {$_('application_name')}
</p> </p>
@ -125,6 +117,11 @@
{$_('send-a-mail-to-lfk-odit-services')} {$_('send-a-mail-to-lfk-odit-services')}
</a> </a>
</div> </div>
<div class="mt-6">
<a
href="/"
class="block w-full text-center py-2 px-3 border border-gray-300 rounded-md text-gray-900 font-medium hover:border-gray-400 focus:outline-none focus:border-gray-400 sm:text-sm">{$_('goback')}</a>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,40 +1,51 @@
<script> <script>
import store from "../store.js"; import store from "../../store.js";
import localForage from "localforage";
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
store.init(); store.init();
import { push, pop, replace } from "svelte-spa-router";
import { OpenAPI, AuthService } from "@odit/lfk-client-js"; import { OpenAPI, AuthService } from "@odit/lfk-client-js";
OpenAPI.BASE = config.baseurl; import Footer from "../general/Footer.svelte";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import "toastify-js/src/toastify.css"; // ------
let usersUsername; let username = "demo";
let usersPassword = ""; let password = "demo";
let is_blocked_by_autologin = false;
let last_loginclick_processed = true; let last_loginclick_processed = true;
localForage.getItem("logindata", (err, value) => {
if (value) {
if (value.access_token && value.refresh_token) {
is_blocked_by_autologin = true;
OpenAPI.TOKEN = value.access_token;
const jwtinfo = JSON.parse(atob(OpenAPI.TOKEN.split(".")[1]));
store.login(value, jwtinfo);
Toastify({
text: $_("welcome_wavinghand"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
}
}
});
const login = async () => { const login = async () => {
// prevent login button spamming // prevent login button spamming
if (last_loginclick_processed) { if (last_loginclick_processed && is_blocked_by_autologin === false) {
last_loginclick_processed = false; last_loginclick_processed = false;
Toastify({ Toastify({
text: $_("login_is_checked"), text: $_("login_is_checked"),
duration: 500, duration: 500,
}).showToast(); }).showToast();
let username = usersUsername;
let password = usersPassword;
// if ((usersUsername + "").length == 0) {
username = config.fallback_username || "demo";
// }
// if ((usersPassword + "").length == 0) {
password = config.fallback_password || "demo";
// }
AuthService.authControllerLogin({ AuthService.authControllerLogin({
username, username,
password, password,
}) })
.then((result) => { .then(async (result) => {
await localForage.setItem("logindata", result);
OpenAPI.TOKEN = result.access_token; OpenAPI.TOKEN = result.access_token;
store.login(result.access_token); const jwtinfo = JSON.parse(atob(OpenAPI.TOKEN.split(".")[1]));
replace("/dashboard/"); store.login(result.access_token, jwtinfo);
location.replace("/");
Toastify({ Toastify({
text: $_("welcome_wavinghand"), text: $_("welcome_wavinghand"),
duration: 500, duration: 500,
@ -62,41 +73,41 @@
}).showToast(); }).showToast();
} }
}; };
function handleKeydown(e) {
if (e.keyCode === 13) {
login();
}
}
</script> </script>
<div class="min-h-screen flex items-center justify-center bg-gray-100"> <div
<div class="max-w-md w-full py-12 px-6"> class="min-h-screen flex items-center justify-center bg-gray-100 text-gray-900">
<img <div class="max-w-md w-full py-12 px-6" role="main">
style="height:10rem;" <img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" />
class="mx-auto" <p class="mt-6 text-lg text-center font-bold">{$_('application_name')}</p>
src="https://lauf-fuer-kaya.de/Bilder/kaya-logo-quadrat.png" <p class="mt-6 text-sm text-center">{$_('log_in_to_your_account')}</p>
alt="" />
<p class="mt-6 text-lg text-center font-bold text-gray-900">
{$_('application_name')}
</p>
<p class="mt-6 text-sm text-center text-gray-900">
{$_('log_in_to_your_account')}
</p>
<div> <div>
<div class="rounded-md shadow-sm"> <div class="rounded-md shadow-sm">
<div> <div>
<!-- svelte-ignore a11y-autofocus -->
<input <input
autofocus
aria-label={$_('email_address_or_username')} aria-label={$_('email_address_or_username')}
name="email" type="text"
type="email"
required="" required=""
class="border-gray-300 placeholder-gray-500 appearance-none rounded-none relative block w-full px-3 py-2 border text-gray-900 rounded-t-md focus:outline-none focus:shadow-outline-blue focus:border-blue-300 focus:z-10 sm:text-sm" class="border-gray-300 placeholder-gray-500 appearance-none rounded-none relative block w-full px-3 py-2 border rounded-t-md focus:outline-none focus:shadow-outline-blue focus:border-blue-300 focus:z-10 sm:text-sm"
on:keydown={handleKeydown}
placeholder={$_('email_address_or_username')} placeholder={$_('email_address_or_username')}
bind:value={usersUsername} /> bind:value={username} />
</div> </div>
<div class="-mt-px relative"> <div class="-mt-px relative">
<input <input
aria-label={$_('password')} aria-label={$_('password')}
name="password"
type="password" type="password"
required="" required=""
bind:value={usersPassword} bind:value={password}
class="border-gray-300 placeholder-gray-500 appearance-none rounded-none relative block w-full px-3 py-2 border text-gray-900 rounded-b-md focus:outline-none focus:shadow-outline-blue focus:border-blue-300 focus:z-10 sm:text-sm" class="border-gray-300 placeholder-gray-500 appearance-none rounded-none relative block w-full px-3 py-2 border rounded-b-md focus:outline-none focus:shadow-outline-blue focus:border-blue-300 focus:z-10 sm:text-sm"
on:keydown={handleKeydown}
placeholder={$_('password')} /> placeholder={$_('password')} />
</div> </div>
</div> </div>
@ -123,10 +134,11 @@
</div> </div>
<div class="mt-2"> <div class="mt-2">
<a <a
href="/#/forgot_password" href="/forgot_password"
class="block w-full text-center py-2 px-3 border border-gray-300 rounded-md text-gray-900 font-medium hover:border-gray-400 focus:outline-none focus:border-gray-400 sm:text-sm"> class="block w-full text-center py-2 px-3 border border-gray-300 rounded-md font-medium hover:border-gray-400 focus:outline-none focus:border-gray-400 sm:text-sm">
{$_('forgot_password?')} {$_('forgot_password?')}
</a> </a>
</div> </div>
</div> </div>
</div> </div>
<Footer />

View File

@ -0,0 +1,129 @@
<script>
import { AuthService } from "@odit/lfk-client-js";
import { _ } from "svelte-i18n";
import Toastify from "toastify-js";
import "toastify-js/src/toastify.css";
let state = "reset_in_progress";
let password = "";
export let params;
function set_new_password() {
if(password.trim() !== ""){
Toastify({
text: $_('password-reset-in-progress'),
duration: 3500,
}).showToast();
AuthService.authControllerResetPassword(atob(params.resetkey),{ password })
.then((resp) => {
Toastify({
text: $_('password-reset-successful'),
duration: 3500,
}).showToast();
state="reset_success";
})
.catch((err) => {
state="reset_error";
});
} else {
Toastify({
text: $_('please-provide-a-password'),
duration: 3500,
}).showToast();
}
}
</script>
{#if state==="reset_success"}
<div class="min-h-screen flex items-center justify-center bg-gray-100">
<div class="max-w-md w-full py-12 px-6">
<img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" />
<p class="mt-6 text-lg text-center font-bold text-gray-900">
{$_('application_name')}
</p>
<p class="mt-2 mb-2 text-sm text-center text-gray-900 font-bold">
{$_('successful-password-reset')}
</p>
<p class="mt-2 mb-2 text-sm text-center text-gray-900">
{$_('you-can-now-use-your-new-password-to-log-in-to-your-account')}
</p>
<div class="mt-6">
<div class="mt-6">
<a
href="/login/"
class="text-center relative block w-full py-2 px-3 border border-transparent rounded-md text-white font-semibold bg-gray-800 hover:bg-gray-700 focus:bg-gray-900 focus:outline-none focus:shadow-outline sm:text-sm">
{$_('go-to-login')}
</a>
</div>
</div>
</div>
</div>
{:else if state==="reset_error"}
<div class="min-h-screen flex items-center justify-center bg-gray-100">
<div class="max-w-md w-full py-12 px-6">
<img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" />
<p class="mt-6 text-lg text-center font-bold text-gray-900">
{$_('application_name')}
</p>
<p class="mt-2 mb-2 text-sm text-center text-gray-900 font-bold">
{$_('password-reset-failed')}
</p>
<p class="mt-2 mb-2 text-sm text-center text-gray-900">
{$_('please-request-a-new-reset-mail')}
</p>
<div class="mt-6">
<div class="mt-6">
<a
href="/forgot_password/"
class="text-center relative block w-full py-2 px-3 border border-transparent rounded-md text-white font-semibold bg-gray-800 hover:bg-gray-700 focus:bg-gray-900 focus:outline-none focus:shadow-outline sm:text-sm">
{$_('request-a-new-reset-mail')}
</a>
</div>
</div>
</div>
</div>
{:else if state==="reset_in_progress"}
<div class="min-h-screen flex items-center justify-center bg-gray-100">
<div class="max-w-md w-full py-12 px-6">
<img style="height:10rem;" class="mx-auto" src="/lfk-logo.png" alt="" />
<p class="mt-6 text-lg text-center font-bold text-gray-900">
{$_('application_name')}
</p>
<p class="mt-2 mb-4 text-md text-center text-gray-900">
{$_('reset-password')}
</p>
<div>
<div class="rounded-md shadow-sm">
<div>
<input
aria-label={$_('new-password')}
name="password"
type="password"
required=""
class="border-gray-300 placeholder-gray-500 appearance-none rounded-md relative block w-full px-3 py-2 border text-gray-900 focus:outline-none focus:shadow-outline-blue focus:border-blue-300 focus:z-10 sm:text-sm"
placeholder={$_('new-password')}
bind:value={password} />
</div>
</div>
<div class="mt-5">
<button
on:click={set_new_password}
type="submit"
class="relative block w-full py-2 px-3 border border-transparent rounded-md text-white font-semibold bg-gray-800 hover:bg-gray-700 focus:bg-gray-900 focus:outline-none focus:shadow-outline sm:text-sm">
<span class="absolute left-0 inset-y pl-3">
<svg
class="h-5 w-5 text-gray-500"
fill="currentColor"
viewBox="0 0 20 20">
<path
fill-rule="evenodd"
d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z"
clip-rule="evenodd" />
</svg>
</span>
{$_('reset-my-password')}
</button>
</div>
</div>
</div>
</div>
{/if}

View File

@ -55,6 +55,7 @@
</div> </div>
<div> <div>
<!-- svelte-ignore a11y-label-has-associated-control -->
<label class="block text-sm font-medium text-gray-700"> <label class="block text-sm font-medium text-gray-700">
Photo Photo
</label> </label>
@ -71,6 +72,7 @@
</div> </div>
<div> <div>
<!-- svelte-ignore a11y-label-has-associated-control -->
<label class="block text-sm font-medium text-gray-700"> <label class="block text-sm font-medium text-gray-700">
Cover photo Cover photo
</label> </label>

View File

@ -0,0 +1,23 @@
<script>
import { _ } from "svelte-i18n";
</script>
<body class="antialiased font-sans">
<div class="flex min-h-screen">
<div class="w-full bg-white flex items-center justify-center ">
<div class="max-w-sm m-8">
<div class="text-black text-5xl md:text-15xl font-black">
Internal Error
</div>
<div class="w-16 h-1 bg-purple-light my-3 md:my-6" />
<p
class="text-grey-darker text-2xl md:text-3xl font-light mb-8 leading-normal">
Something went wrong in the UI logic
</p>
<a
href="/"
class="bg-transparent text-grey-darkest font-bold uppercase tracking-wide py-3 px-6 border-2 border-grey-light hover:border-grey rounded-lg">{$_('goback')}</a>
</div>
</div>
</div>
</body>

View File

@ -0,0 +1,11 @@
<script>
import { _ } from "svelte-i18n";
export let error;
</script>
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b>
{error}
</span>
</div>

View File

@ -0,0 +1,11 @@
<!-- Transition.svelte -->
<script>
import { router } from "tinro";
import { fade } from "svelte/transition";
</script>
{#key $router.path}
<div in:fade={{ duration: 75 }}>
<slot />
</div>
{/key}

View File

@ -0,0 +1,24 @@
export function getlang(langkeys) {
return {
search: {
placeholder: langkeys.search
},
sort: {
sortAsc: langkeys.sort_column_ascending,
sortDesc: langkeys.sort_column_descending
},
pagination: {
previous: langkeys.previous,
next: langkeys.next,
navigate: (page, pages) => `${langkeys.page} ${page} ${langkeys.of} ${pages}`,
page: (page) => `${langkeys.page} ${page}`,
showing: langkeys.showing,
of: langkeys.of,
to: langkeys.to,
results: langkeys.records
},
loading: langkeys.loading,
noRecordsFound: langkeys.no_matching_records_found,
error: langkeys.an_error_happened_while_fetching_the_data
};
}

View File

@ -0,0 +1,10 @@
/** Dispatch event on click outside of node */
export function clickOutside(node) {
const handleClick = (event) => {
if (event.target.getAttribute('data-id') === 'modal_backdrop') {
node.dispatchEvent(new CustomEvent('click_outside', node));
}
};
document.removeEventListener('click', handleClick, true);
document.addEventListener('click', handleClick, true);
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,438 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import {
GroupContactService,
RunnerTeamService,
RunnerOrganizationService,
} from "@odit/lfk-client-js";
import isEmail from "validator/es/lib/isEmail";
import isMobilePhone from "validator/es/lib/isMobilePhone";
import Toastify from "toastify-js";
export let modal_open;
export let current_contacts;
$: selected_team = [];
let firstname_input;
let lastname_input;
let middlename_input;
let phone_input;
let email_input;
let address_input1;
let address_input2;
let address_zipcode;
let address_city;
let teams = [];
let orgs = [];
RunnerTeamService.runnerTeamControllerGetAll().then((val) => {
teams = val;
});
RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => {
orgs = val;
});
function focus(el) {
el.focus();
}
$: middlename_input_value = "";
$: phone_input_value = "";
$: email_input_value = "";
$: lastname_input_value = "";
$: firstname_input_value = "";
$: address_input1_value = "";
$: address_input2_value = "";
$: address_zipcode_value = "";
$: address_city_value = "";
$: processed_last_submit = true;
$: address_checked = true;
$: isPhoneValidOrEmpty =
(phone_input_value.includes("+") &&
isMobilePhone(
phone_input_value
.replaceAll("(", "")
.replaceAll(")", "")
.replaceAll("-", "")
.replaceAll(" ", "")
)) ||
phone_input_value === "";
$: isEmailValidOrEmpty =
isEmail(email_input_value) || email_input_value === "";
$: isLastnameValid = lastname_input_value.trim().length !== 0;
$: isFirstnameValid = firstname_input_value.trim().length !== 0;
$: isAddress1Valid = address_input1_value.trim().length !== 0;
$: iszipcodevalid = address_zipcode_value.trim().length !== 0;
$: iscityvalid = address_city_value.trim().length !== 0;
$: createbtnenabled =
isFirstnameValid &&
isLastnameValid &&
isEmailValidOrEmpty &&
isPhoneValidOrEmpty &&
((isAddress1Valid && iszipcodevalid && iscityvalid) ||
address_checked === false);
(() => {
document.onkeydown = (e) => {
e = e || window.event;
if (e.key === "Escape") {
modal_open = false;
}
if (e.keyCode === 13) {
if (createbtnenabled === true) {
createbtnenabled = false;
submit();
}
}
};
})();
function submit() {
if (processed_last_submit === true) {
processed_last_submit = false;
const toast = Toastify({
text: "Contact is being added...",
duration: -1,
}).showToast();
let address = {};
if (address_checked === true) {
address = {
address1: address_input1_value,
address2: address_input2_value || "",
postalcode: address_zipcode_value,
city: address_city_value,
country: "DE",
};
}
let postdata = {
groups: selected_team,
firstname: firstname_input_value,
lastname: lastname_input_value,
address,
};
if (middlename_input_value) {
postdata.middlename = middlename_input_value;
}
if (phone_input_value) {
postdata.phone = phone_input_value;
}
if (email_input_value) {
postdata.email = email_input_value;
}
GroupContactService.groupContactControllerPost(postdata)
.then((result) => {
firstname_input_value = "";
lastname_input_value = "";
middlename_input_value = "";
email_input_value = "";
modal_open = false;
//
Toastify({
text: "Contact added",
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
current_contacts.push(result);
current_contacts = current_contacts;
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
//
toast.hideToast();
});
}
}
</script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" />
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span>
<div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<svg
class="h-6 w-6 text-blue-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M2 22a8 8 0 1 1 16 0H2zm8-9c-3.315 0-6-2.685-6-6s2.685-6 6-6 6 2.685 6 6-2.685 6-6 6zm10 4h4v2h-4v-2zm-3-5h7v2h-7v-2zm2-5h5v2h-5V7z" /></svg>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_('create-a-new-contact')}
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
Please provide the required information to add a new contact.
</p>
</div>
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6">
<label
for="firstname"
class="block text-sm font-medium text-gray-700">{$_('first-name')}</label>
<input
use:focus
autocomplete="off"
placeholder={$_('first-name')}
class:border-red-500={!isFirstnameValid}
class:focus:border-red-500={!isFirstnameValid}
class:focus:ring-red-500={!isFirstnameValid}
bind:value={firstname_input_value}
bind:this={firstname_input}
type="text"
name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isFirstnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('first-name-is-required')}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="trackname"
class="block text-sm font-medium text-gray-700">{$_('middle-name')}</label>
<input
autocomplete="off"
placeholder={$_('middle-name')}
bind:value={middlename_input_value}
bind:this={middlename_input}
type="text"
name="trackname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
</div>
<div class="col-span-6">
<label
for="lastname"
class="block text-sm font-medium text-gray-700">Last Name</label>
<input
autocomplete="off"
placeholder="Last Name"
class:border-red-500={!isLastnameValid}
class:focus:border-red-500={!isLastnameValid}
class:focus:ring-red-500={!isLastnameValid}
bind:value={lastname_input_value}
bind:this={lastname_input}
type="text"
name="lastname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isLastnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('last-name-is-required')}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="team"
class="block text-sm font-medium text-gray-700">{$_('team')}</label>
<select
name="team"
multiple
bind:value={selected_team}
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2">
{#each teams as team}
<option value={team.id}>
{team.parentGroup.name}
&gt;
{team.name}
</option>
{/each}
{#each orgs as org}
<option value={org.id}>{org.name}</option>
{/each}
</select>
</div>
<div class="col-span-6">
<label
for="phone"
class="block text-sm font-medium text-gray-700">Phone</label>
<input
autocomplete="off"
placeholder="Phone"
class:border-red-500={!isPhoneValidOrEmpty}
class:focus:border-red-500={!isPhoneValidOrEmpty}
class:focus:ring-red-500={!isPhoneValidOrEmpty}
bind:value={phone_input_value}
bind:this={phone_input}
type="tel"
name="phone"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isPhoneValidOrEmpty}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{@html $_('the-provided-phone-number-is-invalid-less-than-br-greater-than-please-enter-a-valid-international-number')}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="email"
class="block text-sm font-medium text-gray-700">{$_('e-mail-adress')}</label>
<input
autocomplete="off"
placeholder={$_('e-mail-adress')}
class:border-red-500={!isEmailValidOrEmpty}
class:focus:border-red-500={!isEmailValidOrEmpty}
class:focus:ring-red-500={!isEmailValidOrEmpty}
bind:value={email_input_value}
bind:this={email_input}
type="email"
name="email"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isEmailValidOrEmpty}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('valid-email-is-required')}
</span>
{/if}
</div>
<div class="flex items-start">
<div class="flex items-center h-5">
<input
bind:checked={address_checked}
id="comments"
name="comments"
type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
</div>
<div class="ml-3 text-sm">
<label
for="comments"
class="font-medium text-gray-700">Address</label>
</div>
</div>
{#if address_checked === true}
<div class="col-span-6">
<label
for="address1"
class="block text-sm font-medium text-gray-700">Address</label>
<input
autocomplete="off"
placeholder="Address"
class:border-red-500={!isAddress1Valid}
class:focus:border-red-500={!isAddress1Valid}
class:focus:ring-red-500={!isAddress1Valid}
bind:value={address_input1_value}
bind:this={address_input1}
type="text"
name="address1"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isAddress1Valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
Address is required
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="address2"
class="block text-sm font-medium text-gray-700">Apartment,
suite, etc.</label>
<input
autocomplete="off"
placeholder="Apartment, suite, etc."
bind:value={address_input2_value}
bind:this={address_input2}
type="text"
name="address2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
</div>
<div class="col-span-6">
<label
for="zipcode"
class="block text-sm font-medium text-gray-700">ZIP/
postal code</label>
<input
autocomplete="off"
placeholder="ZIP/ postal code"
class:border-red-500={!iszipcodevalid}
class:focus:border-red-500={!iszipcodevalid}
class:focus:ring-red-500={!iszipcodevalid}
bind:value={address_zipcode_value}
bind:this={address_zipcode}
type="text"
name="zipcode"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !iszipcodevalid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
Valid zipcode/ postal code is required
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="city"
class="block text-sm font-medium text-gray-700">City</label>
<input
autocomplete="off"
placeholder="City"
class:border-red-500={!iscityvalid}
class:focus:border-red-500={!iscityvalid}
class:focus:ring-red-500={!iscityvalid}
bind:value={address_city_value}
bind:this={address_city}
type="text"
name="city"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !iscityvalid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
Valid city is required
</span>
{/if}
</div>
{/if}
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('create')}
</button>
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
{$_('cancel')}
</button>
</div>
</div>
</div>
</div>
{/if}

View File

@ -0,0 +1,385 @@
<script>
import { _ } from "svelte-i18n";
import store from "../../store";
import {
GroupContactService,
RunnerTeamService,
RunnerOrganizationService,
} from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import PromiseError from "../base/PromiseError.svelte";
import isEmail from "validator/es/lib/isEmail";
let data_loaded = false;
let orgs = [];
let teams = [];
export let params;
$: delete_triggered = false;
$: original_data = {};
$: editable = {};
$: changes_performed = !(
JSON.stringify(original_data) === JSON.stringify(editable)
);
$: isEmailValid =
(editable.email || "") === "" ||
(editable.email && isEmail(editable.email || ""));
$: isFirstnameValid = editable.firstname !== "";
$: isLastnameValid = editable.lastname !== "";
$: save_enabled =
changes_performed &&
isFirstnameValid &&
isLastnameValid &&
isEmailValid &&
isPhoneValidOrEmpty &&
((isAddress1Valid && iszipcodevalid && iscityvalid) ||
address_checked === false);
const promise = GroupContactService.groupContactControllerGetOne(
params.contact
).then((data) => {
data_loaded = true;
original_data = Object.assign(original_data, data);
editable = Object.assign(editable, original_data);
editable.groups = editable.groups.map((g) => g.id);
original_data.groups = original_data.groups.map((g) => g.id);
editable.address_checked = editable.address.address1 !== null;
original_data.address_checked = editable.address.address1 !== null;
});
RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => {
orgs = val;
});
RunnerTeamService.runnerTeamControllerGetAll().then((val) => {
teams = val;
});
$: isPhoneValidOrEmpty =
editable.phone?.includes("+") ||
editable.phone === "" ||
editable.phone === null;
$: isAddress1Valid = editable.address?.address1?.trim().length !== 0;
$: iszipcodevalid = editable.address?.postalcode?.trim().length !== 0;
$: iscityvalid = editable.address?.city?.trim().length !== 0;
function submit() {
if (data_loaded === true && save_enabled) {
Toastify({
text: $_("contact-is-being-updated"),
duration: 2500,
}).showToast();
editable.address.country = "DE";
if (editable.address_checked === false) {
editable.address = {};
}
if (editable.email) editable.email = editable.email;
if (editable.phone) editable.phone = editable.phone;
if (editable.middlename) editable.middlename = editable.middlename;
GroupContactService.groupContactControllerPut(original_data.id, editable)
.then((resp) => {
Object.assign(original_data, editable);
original_data=original_data;
Toastify({
text: $_("updated-contact"),
duration: 2500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
})
.catch((err) => {});
} else {
}
}
function deleteContact() {
GroupContactService.groupContactControllerRemove(original_data.id, true)
.then((resp) => {
location.replace("./");
})
.catch((err) => {});
}
</script>
{#await promise}
{$_('loading-contact-details')}
{:then}
<section class="container p-5 select-none">
<div class="flex flex-row mb-4">
<div class="w-full">
<nav class="w-full flex">
<ol class="list-none flex flex-row items-center justify-start">
<li class="flex items-center">
<svg
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M2 22a8 8 0 1 1 16 0H2zm8-9c-3.315 0-6-2.685-6-6s2.685-6 6-6 6 2.685 6 6-2.685 6-6 6zm10 4h4v2h-4v-2zm-3-5h7v2h-7v-2zm2-5h5v2h-5V7z" /></svg>
</li>
<li class="flex items-center ml-2">
<a class="mr-2" href="./">{$_('contacts')}</a><svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="h-3 w-3 mr-2 stroke-current"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"><line
x1="5"
y1="12"
x2="19"
y2="12" />
<polyline points="12 5 19 12 12 19" /></svg>
</li>
<li class="flex items-center">
<span class="mr-2">{original_data.firstname}
{original_data.middlename || ''}
{original_data.lastname}</span>
</li>
</ol>
</nav>
</div>
</div>
<div class="mb-8 text-3xl font-extrabold leading-tight">
{original_data.firstname}
{original_data.middlename || ''}
{original_data.lastname}
<span data-id="contact_actions_${editable.id}">
{#if store.state.jwtinfo.userdetails.permissions.includes('CONTACT:DELETE')}
{#if delete_triggered}
<button
on:click={deleteContact}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('confirm-deletion')}</button>
<button
on:click={() => {
delete_triggered = !delete_triggered;
}}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm">{$_('cancel')}</button>
{/if}
{#if !delete_triggered}
<button
on:click={() => {
delete_triggered = true;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('delete-contact')}</button>
{/if}
{/if}
{#if !delete_triggered}
<button
disabled={!save_enabled}
class:opacity-50={!save_enabled}
type="button"
on:click={submit}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('save-changes')}</button>
{/if}
</span>
</div>
<!-- -->
<div class="text-sm w-full">
<label
for="firstname"
class="font-medium text-gray-700">{$_('first-name')}</label>
<input
autocomplete="off"
placeholder={$_('first-name')}
type="text"
class:border-red-500={!isFirstnameValid}
class:focus:border-red-500={!isFirstnameValid}
class:focus:ring-red-500={!isFirstnameValid}
bind:value={editable.firstname}
name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isFirstnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('first-name-is-required')}
</span>
{/if}
</div>
<div class="text-sm w-full">
<label
for="middlename"
class="font-medium text-gray-700">{$_('middle-name')}</label>
<input
autocomplete="off"
placeholder={$_('middle-name')}
type="text"
bind:value={editable.middlename}
name="middlename"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
</div>
<div class="text-sm w-full">
<label
for="lastname"
class="font-medium text-gray-700">{$_('last-name')}</label>
<input
autocomplete="off"
placeholder={$_('last-name')}
type="text"
bind:value={editable.lastname}
class:border-red-500={!isLastnameValid}
class:focus:border-red-500={!isLastnameValid}
class:focus:ring-red-500={!isLastnameValid}
name="lastname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isLastnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('last-name-is-required')}
</span>
{/if}
</div>
<div class="text-sm w-full">
<label
for="email"
class="font-medium text-gray-700">{$_('e-mail-adress')}</label>
<input
autocomplete="off"
placeholder={$_('e-mail-adress')}
type="email"
bind:value={editable.email}
class:border-red-500={!isEmailValid}
class:focus:border-red-500={!isEmailValid}
class:focus:ring-red-500={!isEmailValid}
name="email"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isEmailValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('valid-email-is-required')}
</span>
{/if}
</div>
<div class="text-sm w-full">
<label for="phone" class="font-medium text-gray-700">{$_('phone')}</label>
<input
autocomplete="off"
placeholder={$_('phone')}
type="tel"
class:border-red-500={!isPhoneValidOrEmpty}
class:focus:border-red-500={!isPhoneValidOrEmpty}
class:focus:ring-red-500={!isPhoneValidOrEmpty}
bind:value={editable.phone}
name="phone"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isPhoneValidOrEmpty}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('valid-international-phone-number-is-required')}
</span>
{/if}
</div>
<div class="text-sm w-full">
<span class="font-medium text-gray-700">{$_('groups')}</span>
<select
bind:value={editable.groups}
name="team"
multiple
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2">
{#each teams as team}
<option value={team.id}>
{team.parentGroup.name}
&gt;
{team.name}
</option>
{/each}
{#each orgs as org}
<option value={org.id}>{org.name}</option>
{/each}
</select>
</div>
<!-- -->
<div class="flex items-start mt-2">
<div class="flex items-center h-5">
<input
bind:checked={editable.address_checked}
id="comments"
name="comments"
type="checkbox"
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
</div>
<div class="ml-3 text-sm">
<label
for="comments"
class="font-medium text-gray-700">{$_('address')}</label>
</div>
</div>
{#if editable.address_checked === true}
<div class="col-span-6">
<label
for="address1"
class="block text-sm font-medium text-gray-700">{$_('address')}</label>
<input
autocomplete="off"
placeholder="Address"
class:border-red-500={!isAddress1Valid}
class:focus:border-red-500={!isAddress1Valid}
class:focus:ring-red-500={!isAddress1Valid}
bind:value={editable.address.address1}
type="text"
name="address1"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isAddress1Valid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('address-is-required')}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="address2"
class="block text-sm font-medium text-gray-700">{$_('apartment-suite-etc')}</label>
<input
autocomplete="off"
placeholder={$_('apartment-suite-etc')}
bind:value={editable.address.address2}
type="text"
name="address2"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
</div>
<div class="col-span-6">
<label
for="zipcode"
class="block text-sm font-medium text-gray-700">{$_('zip-postal-code')}</label>
<input
autocomplete="off"
placeholder={$_('zip-postal-code')}
class:border-red-500={!iszipcodevalid}
class:focus:border-red-500={!iszipcodevalid}
class:focus:ring-red-500={!iszipcodevalid}
bind:value={editable.address.postalcode}
type="text"
name="zipcode"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !iszipcodevalid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('valid-zipcode-postal-code-is-required')}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="city"
class="block text-sm font-medium text-gray-700">{$_('city')}</label>
<input
autocomplete="off"
placeholder={$_('city')}
class:border-red-500={!iscityvalid}
class:focus:border-red-500={!iscityvalid}
class:focus:ring-red-500={!iscityvalid}
bind:value={editable.address.city}
type="text"
name="city"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !iscityvalid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('valid-city-is-required')}
</span>
{/if}
</div>
{/if}
</section>
{:catch error}
<PromiseError {error} />
{/await}

View File

@ -0,0 +1,29 @@
<script>
import { _ } from "svelte-i18n";
import store from "../../store";
import AddContactModal from "./AddContactModal.svelte";
import ContactsOverview from "./ContactsOverview.svelte";
export let modal_open = false;
let current_contacts = [];
</script>
<section class="container p-5">
<span class="mb-1 text-3xl font-extrabold leading-tight">
{$_('contacts')}
{#if store.state.jwtinfo.userdetails.permissions.includes('CONTACT:CREATE')}
<button
on:click={() => {
modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('create-a-new-contact')}
</button>
{/if}
</span>
<ContactsOverview bind:current_contacts />
</section>
{#if store.state.jwtinfo.userdetails.permissions.includes('CONTACT:CREATE')}
<AddContactModal bind:current_contacts bind:modal_open />
{/if}

View File

@ -0,0 +1,17 @@
<script>
import { _ } from "svelte-i18n";
import AddContactModal from "./AddContactModal.svelte";
import team_empty from "../teams/team_empty.svg";
let modal_open = false;
let current_contacts = [];
</script>
<div class="text-center items-center justify-center">
<p class="mb-16 text-lg text-gray-500">
<img class="w-full h-44" src={team_empty} alt="" />
<span class="font-bold">{$_('there-are-no-contacts-added-yet')}</span><br />
<span>{$_('add-your-first-contact')}</span>
</p>
</div>
<AddContactModal bind:modal_open bind:current_contacts />

View File

@ -0,0 +1,177 @@
<script>
import { _ } from "svelte-i18n";
import Toastify from "toastify-js";
import { GroupContactService } from "@odit/lfk-client-js";
const promise = GroupContactService.groupContactControllerGetAll().then(
(result) => {
current_contacts = result;
}
);
import store from "../../store";
import ContactsEmptyState from "./ContactsEmptyState.svelte";
$: searchvalue = "";
$: active_deletes = [];
export let current_contacts = [];
</script>
{#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:GET')}
{#await promise}
<div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert">
<p class="font-bold">contacts are being loaded...</p>
<p class="text-sm">{$_('this-might-take-a-moment')}</p>
</div>
{:then}
{#if current_contacts.length === 0}
<ContactsEmptyState />
{:else}
<input
type="search"
bind:value={searchvalue}
placeholder={$_('datatable.search')}
aria-label={$_('datatable.search')}
class="gridjs-input gridjs-search-input mb-4" />
<div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
<table class="divide-y divide-gray-200 w-full">
<thead class="bg-gray-50">
<tr>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Name
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Groups
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Address
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">Action</span>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
{#each current_contacts as t}
{#if Object.values(t)
.toString()
.toLowerCase()
.includes(searchvalue)}
<tr data-rowid="team_{t.id}">
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{t.firstname}
{t.middlename || ''}
{t.lastname}
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{#if t.groups.length > 0}
{#each t.groups as g}
{#if g.responseType === 'RUNNERORGANIZATION'}
<a
href="../orgs/{g.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{g.name}</a>
{:else}
<a
href="../teams/{g.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{g.parentGroup.name}
&gt;
{g.name}</a>
{/if}
{/each}
{:else}
{$_('contact-is-not-a-member-in-any-group')}
{/if}
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{#if t.address.address1 !== null}
{t.address.address1}<br />
{t.address.address2 || ''}<br />
{t.address.postalcode}
{t.address.city}
{t.address.country}
{/if}
</div>
</div>
</div>
</td>
{#if active_deletes[t.id] === true}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
on:click={() => {
active_deletes[t.id] = false;
}}
tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">{$_('cancel-delete')}</button>
<button
on:click={() => {
GroupContactService.groupContactControllerRemove(t.id, false).then(
(resp) => {
current_contacts = current_contacts.filter(
(obj) => obj.id !== t.id
);
Toastify({
text: 'Contact deleted',
duration: 500,
backgroundColor:
'linear-gradient(to right, #00b09b, #96c93d)',
}).showToast();
}
);
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button>
</td>
{:else}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<a
href="./{t.id}"
class="text-indigo-600 hover:text-indigo-900">{$_('edit')}</a>
{#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:DELETE')}
<button
on:click={() => {
active_deletes[t.id] = true;
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button>
{/if}
</td>
{/if}
</tr>
{/if}
{/each}
</tbody>
</table>
</div>
{/if}
{:catch error}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b>
{error}
</span>
</div>
{/await}
{/if}

View File

@ -0,0 +1,199 @@
<script>
import { _ } from "svelte-i18n";
import localForage from "localforage";
import store from "../../store";
import { router } from "tinro";
import NoComponentLoaded from "../base/NoComponentLoaded.svelte";
import { AuthService } from "@odit/lfk-client-js";
let dropdown1 = false;
$: navOpen = false;
function logout() {
localForage.clear();
location.replace("/");
}
</script>
<section class="min-h-screen bg-gray-50">
<nav
class:-translate-x-full={!navOpen}
class:translate-x-0={navOpen}
class="select-none fixed top-0 left-0 z-20 h-full pb-10 overflow-x-hidden overflow-y-auto transition origin-left transform border-r w-60 md:translate-x-0 bg-gray-50">
<a href="/" class="flex items-center px-4 py-5">
<img src="/lfk-logo.png" alt="Logo" class="h-10" />
<h3 class="text-lg">Lauf für Kaya! Admin</h3>
</a>
<nav class="text-sm font-medium text-gray-600" aria-label="Main Navigation">
<a
class:bg-gray-100={$router.path === '/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor">
<path
d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z" />
</svg>
<span>{$_('dashboard-title')}</span>
</a>
{#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:GET')}
<a
class:bg-gray-100={$router.path.includes('/orgs/')}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/orgs/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M17 19h2v-8h-6v8h2v-6h2v6zM3 19V4a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v5h2v10h1v2H2v-2h1zm4-8v2h2v-2H7zm0 4v2h2v-2H7zm0-8v2h2V7H7z" /></svg>
<span>{$_('orgs')}</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('USER:GET')}
<a
class:bg-gray-100={$router.path === '/users/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/users/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"><path
fill="currentColor"
d="M610.5 341.3c2.6-14.1 2.6-28.5 0-42.6l25.8-14.9c3-1.7 4.3-5.2 3.3-8.5-6.7-21.6-18.2-41.2-33.2-57.4-2.3-2.5-6-3.1-9-1.4l-25.8 14.9c-10.9-9.3-23.4-16.5-36.9-21.3v-29.8c0-3.4-2.4-6.4-5.7-7.1-22.3-5-45-4.8-66.2 0-3.3.7-5.7 3.7-5.7 7.1v29.8c-13.5 4.8-26 12-36.9 21.3l-25.8-14.9c-2.9-1.7-6.7-1.1-9 1.4-15 16.2-26.5 35.8-33.2 57.4-1 3.3.4 6.8 3.3 8.5l25.8 14.9c-2.6 14.1-2.6 28.5 0 42.6l-25.8 14.9c-3 1.7-4.3 5.2-3.3 8.5 6.7 21.6 18.2 41.1 33.2 57.4 2.3 2.5 6 3.1 9 1.4l25.8-14.9c10.9 9.3 23.4 16.5 36.9 21.3v29.8c0 3.4 2.4 6.4 5.7 7.1 22.3 5 45 4.8 66.2 0 3.3-.7 5.7-3.7 5.7-7.1v-29.8c13.5-4.8 26-12 36.9-21.3l25.8 14.9c2.9 1.7 6.7 1.1 9-1.4 15-16.2 26.5-35.8 33.2-57.4 1-3.3-.4-6.8-3.3-8.5l-25.8-14.9zM496 368.5c-26.8 0-48.5-21.8-48.5-48.5s21.8-48.5 48.5-48.5 48.5 21.8 48.5 48.5-21.7 48.5-48.5 48.5zM96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm224 32c1.9 0 3.7-.5 5.6-.6 8.3-21.7 20.5-42.1 36.3-59.2 7.4-8 17.9-12.6 28.9-12.6 6.9 0 13.7 1.8 19.6 5.3l7.9 4.6c.8-.5 1.6-.9 2.4-1.4 7-14.6 11.2-30.8 11.2-48 0-61.9-50.1-112-112-112S208 82.1 208 144c0 61.9 50.1 112 112 112zm105.2 194.5c-2.3-1.2-4.6-2.6-6.8-3.9-8.2 4.8-15.3 9.8-27.5 9.8-10.9 0-21.4-4.6-28.9-12.6-18.3-19.8-32.3-43.9-40.2-69.6-10.7-34.5 24.9-49.7 25.8-50.3-.1-2.6-.1-5.2 0-7.8l-7.9-4.6c-3.8-2.2-7-5-9.8-8.1-3.3.2-6.5.6-9.8.6-24.6 0-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h255.4c-3.7-6-6.2-12.8-6.2-20.3v-9.2zM173.1 274.6C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg>
<span>{$_('users')}</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:GET')}
<a
class:bg-gray-100={$router.path === '/runners/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/runners/">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M9.83 8.79L8 9.456V13H6V8.05h.015l5.268-1.918c.244-.093.51-.14.782-.131a2.616 2.616 0 0 1 2.427 1.82c.186.583.356.977.51 1.182A4.992 4.992 0 0 0 19 11v2a6.986 6.986 0 0 1-5.402-2.547l-.581 3.297L15 15.67V23h-2v-5.986l-2.05-1.987-.947 4.298-6.894-1.215.348-1.97 4.924.868L9.83 8.79zM13.5 5.5a2 2 0 1 1 0-4 2 2 0 0 1 0 4z" /></svg>
<span>{$_('runners')}</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:GET')}
<a
class:bg-gray-100={$router.path === '/teams/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/teams/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"><path
fill="currentColor"
d="M96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm448 0c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm32 32h-64c-17.6 0-33.5 7.1-45.1 18.6 40.3 22.1 68.9 62 75.1 109.4h66c17.7 0 32-14.3 32-32v-32c0-35.3-28.7-64-64-64zm-256 0c61.9 0 112-50.1 112-112S381.9 32 320 32 208 82.1 208 144s50.1 112 112 112zm76.8 32h-8.3c-20.8 10-43.9 16-68.5 16s-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48v-28.8c0-63.6-51.6-115.2-115.2-115.2zm-223.7-13.4C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg>
<span>{$_('teams')}</span>
</a>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('TRACK:GET')}
<a
class:bg-gray-100={$router.path === '/tracks/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/tracks/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"><path
fill="currentColor"
d="M635.7 167.2L556.1 31.7c-8.8-15-28.3-20.1-43.5-11.5l-69 39.1L503.3 161c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L416 75l-55.2 31.3 27.9 47.4c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L333.2 122 278 153.3 337.8 255c2.2 3.7.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9l-59.7-101.7-55.2 31.3 27.9 47.4c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9l-27.9-47.5-55.2 31.3 59.7 101.7c2.2 3.7.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L84.9 262.9l-69 39.1C.7 310.7-4.6 329.8 4.2 344.8l79.6 135.6c8.8 15 28.3 20.1 43.5 11.5L624.1 210c15.2-8.6 20.4-27.8 11.6-42.8z" /></svg>
<span>{$_('tracks')}</span>
</a>
{/if}
<a
class:bg-gray-100={$router.path === '/contacts/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/contacts/">
<svg fill="currentColor" class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M2 22a8 8 0 1 1 16 0H2zm8-9c-3.315 0-6-2.685-6-6s2.685-6 6-6 6 2.685 6 6-2.685 6-6 6zm10 4h4v2h-4v-2zm-3-5h7v2h-7v-2zm2-5h5v2h-5V7z"/></svg>
<span>{$_('contacts')}</span>
</a>
<a
class:bg-gray-100={$router.path === '/settings/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/settings/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor">
<path
fill-rule="evenodd"
d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z"
clip-rule="evenodd" />
</svg>
<span>{$_('settings')}</span>
</a>
<a
class:bg-gray-100={$router.path === '/about/'}
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
href="/about/">
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" />
<path d="M12 16v-4M12 8h.01" /></svg>
<span>{$_('about')}</span>
</a>
<span
tabindex="0"
class="flex items-center px-4 py-3 transition cursor-pointer group hover:bg-gray-100 hover:text-gray-900"
on:click={() => {
AuthService.authControllerLogout();
logout();
}}>
<svg
class="flex-shrink-0 w-5 h-5 mr-2 text-gray-400 transition group-hover:text-gray-600"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2a9.985 9.985 0 0 1 8 4h-2.71a8 8 0 1 0 .001 12h2.71A9.985 9.985 0 0 1 12 22zm7-6v-3h-8v-2h8V8l5 4-5 4z" /></svg>
<span>{$_('logout')}</span>
</span>
</nav>
</nav>
<div class="ml-0 transition md:ml-60">
<header on:click={() => {
navOpen = true;
}} class="flex items-center justify-between w-full px-4 bg-white border-b h-14 md:hidden"><button class="block btn btn-light md:hidden">
<span class="sr-only">Menu</span><svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentcolor"><path fill-rule="evenodd" d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4A1 1 0 013 5zm0 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" clip-rule="evenodd"></path></svg></button></header>
<slot>
<NoComponentLoaded />
</slot>
</div>
<div
on:click={() => {
navOpen = false;
}}
class:hidden={!navOpen}
class="fixed inset-0 z-10 w-screen h-screen bg-black bg-opacity-25 md:hidden" />
</section>

View File

@ -0,0 +1,23 @@
<script>
import { _ } from "svelte-i18n";
import StatCards from "./StatCards.svelte";
import store from "../../store";
let navOpen = false;
</script>
<div
class="p-5 overflow-x-hidden"
on:click={() => {
navOpen = false;
}}>
<!-- <div class="border-4 border-dashed rounded h-96" /> -->
<h1 class="text-3xl leading-tight">
<span class="font-extrabold">{$_('dashboard-title')}</span> <span>
-
{$_('dashboard-greeting')},
<span
class="text-blue-500">{store.state.jwtinfo.userdetails.firstname}</span>
👋</span>
</h1>
<StatCards />
</div>

View File

@ -0,0 +1,165 @@
<script>
import { StatsService } from "@odit/lfk-client-js";
import { _ } from "svelte-i18n";
const stats_promise = StatsService.statsControllerGet();
</script>
<!-- -->
<h1>{$_('general-stats')}</h1>
{#await stats_promise}
<div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert">
<p class="font-bold">{$_('stats-are-being-loaded')}</p>
<p class="text-sm">{$_('this-might-take-a-moment')}</p>
</div>
{:then stats}
<div
class="flex flex-col lg:flex-row w-full lg:space-x-2 space-y-2 lg:space-y-0 mb-2 lg:mb-4">
<a href="/runners/" class="w-full lg:w-1/4">
<div
class="widget w-full p-4 rounded-lg bg-white border border-grey-100">
<div class="flex flex-row items-center justify-between">
<div class="flex flex-col">
<div class="text-xs uppercase font-light text-grey-500">
{$_('runners')}
</div>
<div class="text-xl font-bold">{stats.total_runners}</div>
</div>
<svg
height="24"
width="24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none" />
<path
d="M13.49 5.48c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm-3.6 13.9l1-4.4 2.1 2v6h2v-7.5l-2.1-2 .6-3c1.3 1.5 3.3 2.5 5.5 2.5v-2c-1.9 0-3.5-1-4.3-2.4l-1-1.6c-.4-.6-1-1-1.7-1-.3 0-.5.1-.8.1l-5.2 2.2v4.7h2v-3.4l1.8-.7-1.6 8.1-4.9-1-.4 2 7 1.4z" /></svg>
</div>
</div>
</a>
<div class="w-full lg:w-1/4">
<div
class="widget w-full p-4 rounded-lg bg-white border border-grey-100">
<div class="flex flex-row items-center justify-between">
<div class="flex flex-col">
<div class="text-xs uppercase font-light text-grey-500">
{$_('total-scans')}
</div>
<div class="text-xl font-bold">{stats.total_scans}</div>
</div><svg
stroke="currentColor"
fill="currentColor"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
size="24"
class="stroke-current text-grey-500"
height="24"
width="24"
xmlns="http://www.w3.org/2000/svg"><polyline
points="22 12 18 12 15 21 9 3 6 12 2 12" /></svg>
</div>
</div>
</div>
<div class="w-full lg:w-1/4">
<div
class="widget w-full p-4 rounded-lg bg-white border border-grey-100">
<div class="flex flex-row items-center justify-between">
<div class="flex flex-col">
<div class="text-xs uppercase font-light text-grey-500">
{$_('total-donations')}
</div>
<div class="text-xl font-bold">{stats.total_donation}</div>
</div><svg
xmlns="http://www.w3.org/2000/svg"
height="24"
fill="currentColor"
width="24"><path d="M0 0h24v24H0z" fill="none" />
<path
d="M15 18.5A6.48 6.48 0 019.24 15H15v-2H8.58c-.05-.33-.08-.66-.08-1s.03-.67.08-1H15V9H9.24A6.491 6.491 0 0115 5.5c1.61 0 3.09.59 4.23 1.57L21 5.3A8.955 8.955 0 0015 3c-3.92 0-7.24 2.51-8.48 6H3v2h3.06a8.262 8.262 0 000 2H3v2h3.52c1.24 3.49 4.56 6 8.48 6 2.31 0 4.41-.87 6-2.3l-1.78-1.77c-1.13.98-2.6 1.57-4.22 1.57z" /></svg>
</div>
</div>
</div>
<div class="w-full lg:w-1/4">
<div
class="widget w-full p-4 rounded-lg bg-white border border-grey-100">
<div class="flex flex-row items-center justify-between">
<div class="flex flex-col">
<div class="text-xs uppercase font-light text-grey-500">
{$_('total-distance')}
</div>
<div class="text-xl font-bold">
{stats.total_distance / 1000}
km
</div>
</div>
<svg
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
height="24"
width="24"><path d="M0 0h24v24H0z" fill="none" />
<path
d="M21 6H3c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 10H3V8h2v4h2V8h2v4h2V8h2v4h2V8h2v4h2V8h2v8z" /></svg>
</div>
</div>
</div>
<a href="/teams/" class="w-full lg:w-1/4">
<div
class="widget w-full p-4 rounded-lg bg-white border border-grey-100">
<div class="flex flex-row items-center justify-between">
<div class="flex flex-col">
<div class="text-xs uppercase font-light text-grey-500">
{$_('count_teams')}
</div>
<div class="text-xl font-bold">{stats.total_teams}</div>
</div>
<svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
size="24"
class="stroke-current text-grey-500"
height="24"
width="24"
xmlns="http://www.w3.org/2000/svg"><path
d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
<circle cx="9" cy="7" r="4" />
<path d="M23 21v-2a4 4 0 0 0-3-3.87" />
<path d="M16 3.13a4 4 0 0 1 0 7.75" /></svg>
</div>
</div>
</a>
<a href="/orgs/" class="w-full lg:w-1/4">
<div
class="widget w-full p-4 rounded-lg bg-white border border-grey-100">
<div class="flex flex-row items-center justify-between">
<div class="flex flex-col">
<div class="text-xs uppercase font-light text-grey-500">
{$_('count_organizations')}
</div>
<div class="text-xl font-bold">{stats.total_orgs}</div>
</div>
<svg
height="24"
fill="currentColor"
width="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M17 11V3H7v4H3v14h8v-4h2v4h8V11h-4zM7 19H5v-2h2v2zm0-4H5v-2h2v2zm0-4H5V9h2v2zm4 4H9v-2h2v2zm0-4H9V9h2v2zm0-4H9V5h2v2zm4 8h-2v-2h2v2zm0-4h-2V9h2v2zm0-4h-2V5h2v2zm4 12h-2v-2h2v2zm0-4h-2v-2h2v2z" /></svg>
</div>
</div>
</a>
</div>
{:catch error}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b>
{error}
</span>
</div>
{/await}

View File

@ -0,0 +1,222 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
export let modal_open;
(function () {
document.onkeydown = function (e) {
e = e || window.event;
if (e.key === "Escape") {
modal_open = false;
}
};
})();
const license_promise = fetch("/licenses.json");
let licenses = [];
$: currentlicense = "";
$: licensetext = "";
license_promise
.then((response) => response.json())
.then((json) => {
licenses = json;
});
</script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" />
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span>
<div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<svg
fill="currentColor"
class="h-6 w-6 text-blue-600"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M14 20v2H2v-2h12zM14.586.686l7.778 7.778L20.95 9.88l-1.06-.354L17.413 12l5.657 5.657-1.414 1.414L16 13.414l-2.404 2.404.283 1.132-1.415 1.414-7.778-7.778 1.415-1.414 1.13.282 6.294-6.293-.353-1.06L14.586.686z" /></svg>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium">
{$_('read-license')}
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">{currentlicense}</p>
</div>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">{licensetext}</p>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('close')}
</button>
</div>
</div>
</div>
</div>
{/if}
<!-- /// -->
<div class="pt-12 px-4 sm:px-6 lg:px-8 lg:pt-20 bg-gray-900 pb-12">
<div class="text-center mb-8">
<h1
class="mt-9 font-display text-4xl leading-none font-semibold text-white sm:text-5xl lg:text-6xl">
{$_('about')}
🧾
</h1>
<p
class="mt-2 max-w-xl mx-auto text-xl lg:max-w-3xl lg:text-2xl text-gray-300">
Lauf für Kaya!
<strong class="text-white font-medium">
{$_('by')}
<a href="https://odit.services" class="underline">ODIT.Services</a>
</strong>
<br />
<span class="text-lg">{$_('lfk-is-os')}</span>
</p>
</div>
</div>
<div class="pt-0 pb-16 overflow-hidden lg:pt-12 lg:py-24">
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
<h2 class="text-4xl font-display font-semibold md:text-5xl">
{$_('credits')}
</h2>
<div class="max-w-3xl mx-auto text-xl leading-8 font-medium mt-8">
<p class="text-center">{$_('oss_credit_description')}</p>
</div>
<div class="w-screen leading-8 pl-5 mt-5">
{#await license_promise}
<p class="text-center w-full">{$_('licenses-are-being-loaded')}</p>
{:then}
<table>
<thead>
<tr>
<th>{$_('dependency_name')}</th>
<th>{$_('license')}</th>
<th>{$_('repo_link')}</th>
<th>{$_('installed-version')}</th>
<th>{$_('author')}</th>
</tr>
</thead>
<tbody>
{#each licenses as l}
<tr>
<td>{l.name}</td>
<td>
{l.license || '?'}<br /><span
class="underline cursor-pointer"
on:click={() => {
modal_open = true;
currentlicense = l.name + '@' + l.version;
licensetext = l.licensetext || $_('no-license-text-could-be-found');
}}>{$_('read-license')}</span>
</td>
<td>
{(l.repo?.url || l.repo)
.replace('git+', '')
.replace('git://', '')}
</td>
<td>{l.version || '?'}</td>
<td>{l.author?.name || l.author || '?'}</td>
</tr>
{/each}
</tbody>
</table>
{:catch error}
<div
class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b>
{error}
</span>
</div>
{/await}
</div>
<div class="w-full leading-8 mt-8">
<p class="text-xl font-medium">{$_('icon-image-credits')}</p>
<ul class="list-disc">
<li>
<a
class="underline"
target="_blank"
rel="noopener noreferrer"
href="https://storyset.com">https://storyset.com</a>
</li>
<li>
<a
class="underline"
target="_blank"
rel="noopener noreferrer"
href="https://undraw.co">https://undraw.co</a>
</li>
<li>
<a
class="underline"
target="_blank"
rel="noopener noreferrer"
href="https://remixicon.com">https://remixicon.com</a>
</li>
</ul>
</div>
<h2 class="mt-4 text-4xl font-display font-semibold md:text-5xl">
{$_('faq')}
</h2>
<div class="mt-6 border-t-2 border-gray-100 pt-10">
<dl class="md:grid md:grid-cols-2 md:gap-8">
<div>
<div>
<dt class="text-lg leading-6 font-medium">Q</dt>
<dd class="mt-2">
<p class="text-base text-gray-500">A</p>
</dd>
</div>
</div>
<div class="mt-12 sm:mt-0">
<div id="team-pricing">
<dt class="text-lg leading-6 font-medium">Q</dt>
<dd class="mt-2">
<p class="text-base text-gray-500">A</p>
</dd>
</div>
<div class="mt-12">
<dt class="text-lg leading-6 font-medium">Q</dt>
<dd class="mt-2">
<p class="text-base text-gray-500">A</p>
</dd>
</div>
</div>
</dl>
</div>
</div>
</div>

View File

@ -0,0 +1,40 @@
<script>
import { onMount } from "svelte";
import { _ } from "svelte-i18n";
$: releaseinfo = "";
onMount(() => {
releaseinfo = document
.getElementById("buildinfo")
.textContent.replace("RELEASE_INFO-", "")
.replace("-RELEASE_INFO", "");
});
const year = new Date().getFullYear();
</script>
<footer class="p-5 w-full">
<p class="text-sm text-gray-500 mt-4">
Lauf für Kaya! Läufersystem - Copyright ©
{year}
+ proudly powered by
<a
class="underline"
href="https://odit.services"
rel="noopener,noreferrer"
target="_blank">ODIT.Services</a>
</p>
<p class="text-sm text-gray-500 mt-4">
<a
class="underline"
target="_blank"
rel="noopener, noreferrer"
href="https://git.odit.services/lfk/frontend/">LfK!Frontend</a>@<a
class="underline"
target="_blank"
rel="noopener, noreferrer"
href="https://git.odit.services/lfk/frontend/src/tag/{releaseinfo}">{releaseinfo}</a>
-
<a class="underline" href="/privacy">{$_('privacy')}</a>
-
<a class="underline" href="/imprint">{$_('imprint')}</a>
</p>
</footer>

View File

@ -0,0 +1,45 @@
<script>
import { _, getLocaleFromNavigator } from "svelte-i18n";
import marked from "marked";
import Footer from "./Footer.svelte";
import * as css from "../base/simple.css";
let html = "";
async function load() {
let md = await fetch("/imprint_" + getLocaleFromNavigator() + ".md");
if (!md.ok) {
md = await fetch("/imprint_en.md");
}
html = marked(await md.text());
}
const promise = load();
</script>
<div class="pt-12 px-4 sm:px-6 lg:px-8 lg:pt-20 bg-gray-900 pb-12">
<div class="text-center mb-8">
<h1
class="mt-9 font-display text-4xl leading-none font-semibold text-white sm:text-5xl lg:text-6xl">
{$_('imprint')}
</h1>
</div>
</div>
<div class="pt-0 pb-16 overflow-hidden lg:pt-12 lg:py-24">
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
{#await promise}
<p class="text-center w-full">{$_('imprint-loading')}</p>
{:then}
<div class="simplecontent">
{@html html}
</div>
{:catch error}
<div
class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b>
{error}
</span>
</div>
{/await}
</div>
</div>
<Footer />

View File

@ -3,8 +3,8 @@
</script> </script>
<body class="antialiased font-sans"> <body class="antialiased font-sans">
<div class="md:flex min-h-screen"> <div class="flex min-h-screen">
<div class="w-full md:w-1/2 bg-white flex items-center justify-center "> <div class="w-full bg-white flex items-center justify-center ">
<div class="max-w-sm m-8"> <div class="max-w-sm m-8">
<div class="text-black text-5xl md:text-15xl font-black"> <div class="text-black text-5xl md:text-15xl font-black">
{$_('404title')} {$_('404title')}

View File

@ -0,0 +1,45 @@
<script>
import { _, getLocaleFromNavigator } from "svelte-i18n";
import marked from "marked";
import Footer from "./Footer.svelte";
import * as css from "../base/simple.css";
let html = "";
async function load() {
let md = await fetch("/privacy_" + getLocaleFromNavigator() + ".md");
if (!md.ok) {
md = await fetch("/privacy_en.md");
}
html = marked(await md.text());
}
const promise = load();
</script>
<div class="pt-12 px-4 sm:px-6 lg:px-8 lg:pt-20 bg-gray-900 pb-12">
<div class="text-center mb-8">
<h1
class="mt-9 font-display text-4xl leading-none font-semibold text-white sm:text-5xl lg:text-6xl">
{$_('privacy')}
</h1>
</div>
</div>
<div class="pt-0 pb-16 overflow-hidden lg:pt-12 lg:py-24">
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
{#await promise}
<p class="text-center w-full">{$_('privacy-loading')}</p>
{:then}
<div class="simplecontent">
{@html html}
</div>
{:catch error}
<div
class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b>
{error}
</span>
</div>
{/await}
</div>
</div>
<Footer />

View File

@ -0,0 +1,35 @@
<script>
import { _ } from "svelte-i18n";
import FormLayout from "../base/FormLayout.svelte";
</script>
<div class="pt-12 px-4 sm:px-6 lg:px-8 lg:pt-20 bg-gray-900 pb-12">
<div class="text-center mb-8">
<h1
class="mt-9 font-display text-4xl leading-none font-semibold text-white sm:text-5xl lg:text-6xl">
🔨<br />{$_('settings')}
</h1>
<p
class="mt-2 max-w-xl mx-auto text-xl lg:max-w-3xl lg:text-2xl text-gray-300">
<span class="text-lg">configure your profile however you want</span>
</p>
</div>
</div>
<div class="pt-0 pb-16 bg-gray-50 overflow-hidden lg:pt-12 lg:py-24">
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
<!-- <h2 class="text-4xl font-display font-semibold text-gray-900 md:text-5xl">
General
</h2> -->
<div
class="max-w-3xl mx-auto text-xl leading-8 font-medium text-gray-900 mb-16">
<p class="text-center">
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Temporibus et
amet voluptate nulla accusantium vero blanditiis nobis facere veritatis.
Impedit deserunt saepe aliquid unde consequuntur officia consequatur
fugit iusto dolorem?
</p>
</div>
<FormLayout />
</div>
</div>

View File

@ -0,0 +1,82 @@
<script>
let open = false;
</script>
<div class="md:flex flex-col md:flex-row h-screen w-full">
<div
class="flex flex-col w-full md:w-64 text-gray-700 bg-white dark-mode:text-gray-200 dark-mode:bg-gray-800 flex-shrink-0">
<div
class="flex-shrink-0 px-8 py-4 flex flex-row items-center justify-between">
<a
href="/#/test"
class="text-lg font-semibold tracking-widest text-gray-900 uppercase rounded-lg dark-mode:text-white focus:outline-none focus:shadow-outline">Sidebar</a>
<button
class="rounded-lg md:hidden focus:outline-none focus:shadow-outline">
<svg fill="currentColor" viewBox="0 0 20 20" class="w-6 h-6">
{#if open}
<path
fill-rule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clip-rule="evenodd" />
{/if}
{#if !open}
<path
fill-rule="evenodd"
d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM9 15a1 1 0 011-1h6a1 1 0 110 2h-6a1 1 0 01-1-1z"
clip-rule="evenodd" />
{/if}
</svg>
</button>
</div>
<nav
:class:block={open}
:class:hidden={!open}
class="flex-grow md:block px-4 pb-4 md:pb-0 md:overflow-y-auto">
<a
class="block px-4 py-2 mt-2 text-sm font-semibold text-gray-900 bg-gray-200 rounded-lg dark-mode:bg-gray-700 dark-mode:hover:bg-gray-600 dark-mode:focus:bg-gray-600 dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:text-gray-200 hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline"
href="#">Blog</a>
<a
class="block px-4 py-2 mt-2 text-sm font-semibold text-gray-900 bg-transparent rounded-lg dark-mode:bg-transparent dark-mode:hover:bg-gray-600 dark-mode:focus:bg-gray-600 dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:text-gray-200 hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline"
href="#">Portfolio</a>
<a
class="block px-4 py-2 mt-2 text-sm font-semibold text-gray-900 bg-transparent rounded-lg dark-mode:bg-transparent dark-mode:hover:bg-gray-600 dark-mode:focus:bg-gray-600 dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:text-gray-200 hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline"
href="#">About</a>
<a
class="block px-4 py-2 mt-2 text-sm font-semibold text-gray-900 bg-transparent rounded-lg dark-mode:bg-transparent dark-mode:hover:bg-gray-600 dark-mode:focus:bg-gray-600 dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:text-gray-200 hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline"
href="#">Contact</a>
<div class="relative">
<button
on:click={() => {
open = !open;
}}
class="flex flex-row items-center w-full px-4 py-2 mt-2 text-sm font-semibold text-left bg-transparent rounded-lg dark-mode:bg-transparent dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:focus:bg-gray-600 dark-mode:hover:bg-gray-600 md:block hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline">
<span>Dropdown</span>
<svg
fill="currentColor"
viewBox="0 0 20 20"
class="inline w-4 h-4 mt-1 ml-1 transition-transform duration-200 transform md:-mt-1"><path
fill-rule="evenodd"
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
clip-rule="evenodd" /></svg>
</button>
<div
class:block={open}
class:hidden={!open}
class="absolute right-0 w-full mt-2 origin-top-right rounded-md shadow-lg">
<div
class="px-2 py-2 bg-white rounded-md shadow dark-mode:bg-gray-800">
<a
class="block px-4 py-2 mt-2 text-sm font-semibold bg-transparent rounded-lg dark-mode:bg-transparent dark-mode:hover:bg-gray-600 dark-mode:focus:bg-gray-600 dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:text-gray-200 md:mt-0 hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline"
href="#">Link #1</a>
<a
class="block px-4 py-2 mt-2 text-sm font-semibold bg-transparent rounded-lg dark-mode:bg-transparent dark-mode:hover:bg-gray-600 dark-mode:focus:bg-gray-600 dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:text-gray-200 md:mt-0 hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline"
href="#">Link #2</a>
<a
class="block px-4 py-2 mt-2 text-sm font-semibold bg-transparent rounded-lg dark-mode:bg-transparent dark-mode:hover:bg-gray-600 dark-mode:focus:bg-gray-600 dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:text-gray-200 md:mt-0 hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline"
href="#">Link #3</a>
</div>
</div>
</div>
</nav>
</div>
</div>

View File

@ -0,0 +1,161 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { RunnerOrganizationService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
export let modal_open;
export let current_organizations;
let name_input_dom;
function focus(el) {
el.focus();
}
$: name = "";
$: processed_last_submit = true;
$: isOrgnameValid = name.trim().length !== 0;
$: createbtnenabled = isOrgnameValid;
(() => {
document.onkeydown = (e) => {
e = e || window.event;
if (e.key === "Escape") {
modal_open = false;
}
if (e.keyCode === 13) {
if (createbtnenabled === true) {
createbtnenabled = false;
submit();
}
}
};
})();
function submit() {
if (processed_last_submit === true) {
processed_last_submit = false;
const toast = Toastify({
text: "Organization is being added...",
duration: -1,
}).showToast();
RunnerOrganizationService.runnerOrganizationControllerPost({
name,
address: undefined,
contact: undefined,
})
.then((result) => {
name = "";
modal_open = false;
//
Toastify({
text: "Organization added",
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
current_organizations = current_organizations.concat([result]);
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
//
toast.hideToast();
});
}
}
</script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" />
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span>
<div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<svg
class="h-6 w-6 text-blue-600"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"><path
d="M17 19h2v-8h-6v8h2v-6h2v6zM3 19V4a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v5h2v10h1v2H2v-2h1zm4-8v2h2v-2H7zm0 4v2h2v-2H7zm0-8v2h2V7H7z" /></svg>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
Create a new Organization
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
Please provide the required information to add a new
organization.
</p>
</div>
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6">
<label
for="firstname"
class="block text-sm font-medium text-gray-700">Name</label>
<input
use:focus
autocomplete="off"
placeholder="Name"
class:border-red-500={!isOrgnameValid}
class:focus:border-red-500={!isOrgnameValid}
class:focus:ring-red-500={!isOrgnameValid}
bind:value={name}
bind:this={name_input_dom}
type="text"
name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isOrgnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
Organization name is required
</span>
{/if}
</div>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('create')}
</button>
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
{$_('cancel')}
</button>
</div>
</div>
</div>
</div>
{/if}

View File

@ -0,0 +1,100 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { RunnerOrganizationService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte";
export let modal_open;
export let delete_org;
const dispatch = createEventDispatcher();
function cancelDelete() {
modal_open = false;
dispatch("cancelDelete", { id: delete_org.id });
}
function deleteOrg() {
RunnerOrganizationService.runnerOrganizationControllerRemove(
delete_org.id,
true
)
.then((resp) => {
Toastify({
text: "Organization deleted",
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
location.replace("./");
})
.catch((err) => {
//
});
}
</script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={cancelDelete}>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" />
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span>
<div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<svg
class="h-6 w-6 text-blue-600"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"><path
fill="currentColor"
d="M96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm448 0c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm32 32h-64c-17.6 0-33.5 7.1-45.1 18.6 40.3 22.1 68.9 62 75.1 109.4h66c17.7 0 32-14.3 32-32v-32c0-35.3-28.7-64-64-64zm-256 0c61.9 0 112-50.1 112-112S381.9 32 320 32 208 82.1 208 144s50.1 112 112 112zm76.8 32h-8.3c-20.8 10-43.9 16-68.5 16s-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48v-28.8c0-63.6-51.6-115.2-115.2-115.2zm-223.7-13.4C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
Attention!
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
Do you want to delete the organization
{delete_org.name}?<br />All associated teams and runners will
be deleted too!
</p>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
on:click={deleteOrg}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">
Confirm, delete organization and associated teams+runners.
</button>
<button
on:click={cancelDelete}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
Cancel, keep organization
</button>
</div>
</div>
</div>
</div>
{/if}

View File

@ -0,0 +1,259 @@
<script>
import {
GroupContactService,
RunnerOrganizationService,
} from "@odit/lfk-client-js";
import { _ } from "svelte-i18n";
import Toastify from "toastify-js";
import store from "../../store";
import ConfirmOrgDeletion from "./ConfirmOrgDeletion.svelte";
import ImportRunnerModal from "../runners/ImportRunnerModal.svelte";
import PromiseError from "../base/PromiseError.svelte";
$: delete_triggered = false;
$: save_enabled = !data_changed;
export let params;
let orgdata = {};
let original = {};
let contacts = [];
$: data_loaded = false;
$: data_changed = JSON.stringify(orgdata) === JSON.stringify(original);
const promise = RunnerOrganizationService.runnerOrganizationControllerGetOne(
params.orgid
).then((value) => {
data_loaded = true;
if (value.contact) {
if (value.contact !== "null") {
value.contact = value.contact.id;
}
}
orgdata = Object.assign(orgdata, value);
original = Object.assign(original, value);
});
GroupContactService.groupContactControllerGetAll().then((val) => {
contacts = val;
});
let modal_open = false;
let delete_org = {};
function deleteOrganization() {
RunnerOrganizationService.runnerOrganizationControllerRemove(
original.id,
false
)
.then((resp) => {
Toastify({
text: "Organization deleted",
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
location.replace("./");
})
.catch((err) => {
modal_open = true;
delete_org = original;
});
}
function submit() {
if (data_loaded === true && save_enabled) {
Toastify({
text: "updating organization",
duration: 2500,
}).showToast();
let postdata = orgdata;
postdata.contact = postdata.contact === "null" ? null : postdata.contact;
RunnerOrganizationService.runnerOrganizationControllerPut(
original.id,
postdata
)
.then((resp) => {
Object.assign(original, orgdata);
original = orgdata;
Object.assign(original, orgdata);
//
Toastify({
text: "updated organization",
duration: 2500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
})
.catch((err) => {});
} else {
}
}
export let import_modal_open = false;
</script>
<ImportRunnerModal
on:cancelDelete={(event) => {
import_modal_open = false;
}}
current_runners={[]}
passed_team={{}}
passed_orgs={[]}
passed_org={orgdata}
opened_from="OrgDetail"
bind:import_modal_open />
<ConfirmOrgDeletion bind:modal_open bind:delete_org />
{#if data_loaded}
<section class="container p-5">
<div class="mb-8 text-3xl font-extrabold leading-tight">
{original.name}
<span data-id="org_actions_${orgdata.id}">
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:IMPORT')}
<button
on:click={() => {
import_modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('import-runners')}
</button>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('USER:DELETE')}
{#if delete_triggered}
<button
on:click={deleteOrganization}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('confirm-delete')}</button>
<button
on:click={() => {
delete_triggered = !delete_triggered;
}}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm">{$_('cancel')}</button>
{/if}
{#if !delete_triggered}
<button
on:click={() => {
delete_triggered = true;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('delete-organization')}</button>
{/if}
{/if}
{#if !delete_triggered}
<button
on:click={submit}
disabled={!save_enabled}
class:opacity-50={!save_enabled}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('save-changes')}</button>
{/if}
</span>
</div>
<div class="flex flex-row mb-4">
<div class="w-full">
<nav class="w-full flex">
<ol class="list-none flex flex-row items-center justify-start">
<li class="mr-2 flex items-center">
<svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="h-3 w-3 stroke-current"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"><path
d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
<polyline points="9 22 9 12 15 12 15 22" /></svg>
</li>
<li class="flex items-center">
<a class="mr-2" href="/">Home</a><svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="h-3 w-3 mr-2 stroke-current"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"><line
x1="5"
y1="12"
x2="19"
y2="12" />
<polyline points="12 5 19 12 12 19" /></svg>
</li>
<li class="mr-2 flex items-center">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M21 20h2v2H1v-2h2V3a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v17zm-2 0V4H5v16h14zM8 11h3v2H8v-2zm0-4h3v2H8V7zm0 8h3v2H8v-2zm5 0h3v2h-3v-2zm0-4h3v2h-3v-2zm0-4h3v2h-3V7z" /></svg>
</li>
<li class="flex items-center">
<a class="mr-2" href="./">Orgs</a><svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="h-3 w-3 mr-2 stroke-current"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"><line
x1="5"
y1="12"
x2="19"
y2="12" />
<polyline points="12 5 19 12 12 19" /></svg>
</li>
<li class="flex items-center">
<span class="mr-2">Org-Details #{params.orgid}</span>
</li>
</ol>
</nav>
</div>
</div>
<div class="text-sm w-full">
<label for="name" class="font-medium text-gray-700">Name</label>
<input
autocomplete="off"
placeholder="Name"
type="text"
bind:value={orgdata.name}
name="name"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
</div>
<div class="text-sm w-full">
<label
for="contact"
class="font-medium text-gray-700">{$_('contact')}</label>
<select
name="contact"
bind:value={orgdata.contact}
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2">
<option value="null">no contact</option>
{#each contacts as c}
<option value={c.id}>
{c.firstname}
{c.middlename || ''}
{c.lastname}
</option>
{/each}
</select>
</div>
<div class="text-sm w-full">
<label
for="address"
class="font-medium text-gray-700">{$_('address')}</label>
<input
autocomplete="off"
placeholder={$_('address')}
type="text"
bind:value={orgdata.address}
name="address"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
</div>
</section>
{:else}
{#await promise}
organization detail is being loaded...
{:catch error}
<PromiseError />
{/await}
{/if}

View File

@ -0,0 +1,175 @@
<script>
import { _ } from "svelte-i18n";
let modal_open = false;
let delete_org = {};
import { RunnerOrganizationService } from "@odit/lfk-client-js";
import store from "../../store";
import OrgsEmptyState from "./OrgsEmptyState.svelte";
import Toastify from "toastify-js";
import ConfirmOrgDeletion from "./ConfirmOrgDeletion.svelte";
$: searchvalue = "";
$: active_deletes = [];
export let current_organizations = [];
const promise = RunnerOrganizationService.runnerOrganizationControllerGetAll().then(
(val) => {
current_organizations = val;
}
);
</script>
<ConfirmOrgDeletion
on:cancelDelete={(event) => {
modal_open = false;
active_deletes[event.detail.id] = false;
}}
bind:modal_open
bind:delete_org />
{#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:GET')}
{#await promise}
<div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert">
<p class="font-bold">organizations are being loaded...</p>
<p class="text-sm">{$_('this-might-take-a-moment')}</p>
</div>
{:then}
{#if current_organizations.length === 0}
<OrgsEmptyState />
{:else}
<input
type="search"
bind:value={searchvalue}
placeholder={$_('datatable.search')}
aria-label={$_('datatable.search')}
class="gridjs-input gridjs-search-input mb-4" />
<div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
<table class="divide-y divide-gray-200 w-full">
<thead class="bg-gray-50">
<tr>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Name
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Address
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Contact
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">Action</span>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
{#each current_organizations as o}
{#if Object.values(o)
.toString()
.toLowerCase()
.includes(searchvalue)}
<tr data-rowid="org_{o.id}">
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{o.name}
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{#if o.address}
{JSON.stringify(o.address)}
{:else}no address specified{/if}
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{#if o.contact}
<a
href="../contacts/{o.contact.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{o.contact.firstname}
{o.contact.middlename || ''}
{o.contact.lastname}</a>
{:else}no contact specified{/if}
</div>
</div>
</div>
</td>
{#if active_deletes[o.id] === true}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
on:click={() => {
active_deletes[o.id] = false;
}}
tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">Cancel
Delete</button>
<button
on:click={() => {
RunnerOrganizationService.runnerOrganizationControllerRemove(o.id, false)
.then((resp) => {
current_organizations = current_organizations.filter((obj) => obj.id !== o.id);
Toastify({
text: 'Organization deleted',
duration: 500,
backgroundColor:
'linear-gradient(to right, #00b09b, #96c93d)',
}).showToast();
})
.catch((err) => {
modal_open = true;
delete_org = o;
});
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">Confirm
Delete</button>
</td>
{:else}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<a
href="./{o.id}"
class="text-indigo-600 hover:text-indigo-900">Edit</a>
{#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:DELETE')}
<button
on:click={() => {
active_deletes[o.id] = true;
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">Delete</button>
{/if}
</td>
{/if}
</tr>
{/if}
{/each}
</tbody>
</table>
</div>
{/if}
{:catch error}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b>
{error}
</span>
</div>
{/await}
{/if}

View File

@ -0,0 +1,52 @@
<script>
import { _ } from "svelte-i18n";
import store from "../../store";
import AddOrgModal from "./AddOrgModal.svelte";
export let modal_open = false;
import OrgOverview from "./OrgOverview.svelte";
import ImportRunnerModal from "../runners/ImportRunnerModal.svelte";
let current_organizations = [];
export let import_modal_open = false;
</script>
<section class="container p-5">
<span class="mb-1 text-3xl font-extrabold leading-tight">
{$_('organizations')}
{#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:CREATE')}
<button
on:click={() => {
modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('create-organization')}
</button>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:IMPORT')}
<button
on:click={() => {
import_modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('import-runners')}
</button>
{/if}
</span>
<p class="mb-8 text-lg text-gray-500">manage runner organizations</p>
<OrgOverview bind:current_organizations />
</section>
{#if store.state.jwtinfo.userdetails.permissions.includes('ORGANIZATION:CREATE')}
<AddOrgModal bind:current_organizations bind:modal_open />
<ImportRunnerModal
on:cancelDelete={(event) => {
import_modal_open = false;
}}
passed_team={{}}
passed_org={{}}
passed_orgs={current_organizations}
opened_from="OrgOverview"
current_runners={[]}
bind:import_modal_open />
{/if}

View File

@ -0,0 +1,17 @@
<script>
import { _ } from "svelte-i18n";
import AddOrgModal from "./AddOrgModal.svelte";
import org_empty from "./org_empty.svg";
let modal_open = false;
let current_organizations = [];
</script>
<div class="text-center items-center justify-center">
<p class="mb-16 text-lg text-gray-500">
<img class="w-full h-44" src={org_empty} alt="" />
<span class="font-bold">There are no organizations added yet.</span><br />
<span>Add your first organization</span>
</p>
</div>
<AddOrgModal bind:modal_open bind:current_organizations />

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -0,0 +1,80 @@
<h3 class="text-lg">Standard Avatars</h3>
<div class="relative rounded-full w-4 h-4">
<img
alt=""
src="https://gustui.s3.amazonaws.com/avatar.png"
class="absolute left-0 top-0 w-full h-full rounded-full object-cover" />
</div>
<div class="relative rounded-full w-8 h-8">
<img
alt=""
src="https://gustui.s3.amazonaws.com/avatar.png"
class="absolute left-0 top-0 w-full h-full rounded-full object-cover" />
</div>
<div class="relative rounded-full w-12 h-12">
<img
alt=""
src="https://gustui.s3.amazonaws.com/avatar.png"
class="absolute left-0 top-0 w-full h-full rounded-full object-cover" />
</div>
<div class="relative rounded-full w-16 h-16">
<img
alt=""
src="https://gustui.s3.amazonaws.com/avatar.png"
class="absolute left-0 top-0 w-full h-full rounded-full object-cover" />
</div>
<div class="relative rounded-full w-20 h-20">
<img
alt=""
src="https://gustui.s3.amazonaws.com/avatar.png"
class="absolute left-0 top-0 w-full h-full rounded-full object-cover" />
</div>
<div class="relative rounded-full w-24 h-24">
<img
alt=""
src="https://gustui.s3.amazonaws.com/avatar.png"
class="absolute left-0 top-0 w-full h-full rounded-full object-cover" />
</div>
<h3 class="text-lg">Status Avatars</h3>
<div class="relative rounded-full w-4 h-4">
<img
alt=""
src="https://gustui.s3.amazonaws.com/avatar.png"
class="absolute left-0 top-0 w-full h-full rounded-full object-cover" />
<div class="absolute rounded-full right-0 bottom-0 w-1 h-1 bg-gray-200" />
</div>
<div class="relative rounded-full w-8 h-8">
<img
alt=""
src="https://gustui.s3.amazonaws.com/avatar.png"
class="absolute left-0 top-0 w-full h-full rounded-full object-cover" />
<div class="absolute rounded-full right-0 bottom-0 w-2 h-2 bg-green-400" />
</div>
<div class="relative rounded-full w-12 h-12">
<img
alt=""
src="https://gustui.s3.amazonaws.com/avatar.png"
class="absolute left-0 top-0 w-full h-full rounded-full object-cover" />
<div class="absolute rounded-full right-0 bottom-0 w-4 h-4 bg-red-600" />
</div>
<div class="relative rounded-full w-16 h-16">
<img
alt=""
src="https://gustui.s3.amazonaws.com/avatar.png"
class="absolute left-0 top-0 w-full h-full rounded-full object-cover" />
<div class="absolute rounded-full right-0 bottom-0 w-5 h-5 bg-gray-200" />
</div>
<div class="relative rounded-full w-20 h-20">
<img
alt=""
src="https://gustui.s3.amazonaws.com/avatar.png"
class="absolute left-0 top-0 w-full h-full rounded-full object-cover" />
<div class="absolute rounded-full right-0 bottom-0 w-6 h-6 bg-green-400" />
</div>
<div class="relative rounded-full w-24 h-24">
<img
alt=""
src="https://gustui.s3.amazonaws.com/avatar.png"
class="absolute left-0 top-0 w-full h-full rounded-full object-cover" />
<div class="absolute rounded-full right-0 bottom-0 w-6 h-6 bg-red-600" />
</div>

View File

@ -0,0 +1,53 @@
<h3 class="text-lg">badges</h3>
<span
class="text-sm font-medium bg-green-100 py-1 px-2 rounded text-green-500 align-middle">Paid</span>
<span
class="text-sm font-medium bg-red-100 py-1 px-2 rounded text-red-500 align-middle">Overdue</span>
<span
class="rounded-sm py-1 px-2 text-xs font-medium text-white bg-blue-600">Primary</span>
<span
class="rounded-sm py-1 px-2 text-xs font-medium text-white bg-gray-600">Secondary</span>
<span
class="rounded-sm py-1 px-2 text-xs font-medium text-white bg-green-600">Success</span>
<span
class="rounded-sm py-1 px-2 text-xs font-medium text-white bg-red-600">Danger</span>
<span
class="rounded-sm py-1 px-2 text-xs font-medium text-black bg-yellow-400">Warning</span>
<span
class="rounded-sm py-1 px-2 text-xs font-medium text-white bg-indigo-300">Info</span>
<span
class="rounded-sm py-1 px-2 text-xs font-medium text-black bg-gray-200">Light</span>
<span
class="rounded-sm py-1 px-2 text-xs font-medium text-white bg-gray-900">Dark</span>
<h3 class="text-lg">closable badges</h3>
<span class="rounded-sm py-1 px-2 text-xs font-medium text-white bg-blue-600">
Primary
<span class="ml-2 text-base cursor-pointer">×</span>
</span>
<span class="rounded-sm py-1 px-2 text-xs font-medium text-white bg-gray-600">
Secondary
<span class="ml-2 text-base cursor-pointer">×</span>
</span>
<span class="rounded-sm py-1 px-2 text-xs font-medium text-white bg-green-600">
Success
<span class="ml-2 text-base cursor-pointer">×</span>
</span>
<span class="rounded-sm py-1 px-2 text-xs font-medium text-white bg-red-600">
Danger
<span class="ml-2 text-base cursor-pointer">×</span>
</span>
<span class="rounded-sm py-1 px-2 text-xs font-medium text-black bg-yellow-400">
Warning
<span class="ml-2 text-base cursor-pointer">×</span>
</span>
<span class="rounded-sm py-1 px-2 text-xs font-medium text-white bg-indigo-300">
Info
<span class="ml-2 text-base cursor-pointer">×</span>
</span>
<span class="rounded-sm py-1 px-2 text-xs font-medium text-black bg-gray-200">
Light<span class="ml-2 text-base cursor-pointer">×</span>
</span>
<span class="rounded-sm py-1 px-2 text-xs font-medium text-white bg-gray-900">
Dark
<span class="ml-2 text-base cursor-pointer">×</span>
</span>

View File

@ -0,0 +1,75 @@
<script>
import Avatars from "./Avatars.svelte";
import Badges from "./Badges.svelte";
import BreadcrumbNav from "./BreadcrumbNav.svelte";
import FileUpload from "./FileUpload.svelte";
import Pagination from "./Pagination.svelte";
import Table from "./Table.svelte";
import Tabs from "./Tabs.svelte";
import Tags from "./Tags.svelte";
</script>
<div class="border-4 border-dashed rounded h-96 mb-4" />
<div class="mb-8">
<FileUpload />
</div>
<div class="mb-8">
<Tabs />
</div>
<div class="mb-8">
<Tags />
</div>
<div class="mb-8">
<Badges />
</div>
<div class="mb-8">
<Avatars />
</div>
<Pagination />
<div class="mb-8">
<Table />
</div>
<div
class="widget w-full p-4 mb-4 rounded-lg bg-white border border-grey-100">
<div class="flex flex-row items-center justify-between mb-6">
<div class="flex flex-col">
<div class="text-sm font-light text-grey-500">Regular</div>
<div class="text-sm font-bold"><span>Text inputs</span></div>
</div>
</div>
<div class="flex flex-col lg:flex-row lg:flex-wrap w-full lg:space-x-4">
<div class="w-full lg:w-1/4">
<div class="form-element ">
<div class="form-label">Label</div><input
name="name"
type="text"
class="form-input"
placeholder="Enter something..." />
<div class="form-hint">This is a hint</div>
</div>
</div>
<div class="w-full lg:w-1/4">
<div class="form-element ">
<div class="form-label">First name</div><input
name="name"
type="text"
class="form-input form-input-invalid"
placeholder="john@example.com" />
<div class="form-error">First name is required</div>
</div>
</div>
<div class="w-full lg:w-1/4">
<div class="form-element ">
<div class="form-label">First name</div><input
name="name"
type="text"
class="form-input form-input-valid"
placeholder="john@example.com" />
<div class="form-success">First name is valid</div>
</div>
</div>
</div>
</div>
<div class="mb-8">
<BreadcrumbNav />
</div>

View File

@ -1,4 +1,4 @@
<div class="w-full p-4 rounded-lg bg-white border border-grey-100 dark:bg-grey-895 dark:border-grey-890"> <div class="w-full p-4 rounded-lg bg-white border border-grey-100">
<div class="flex flex-row items-center justify-between mb-6"> <div class="flex flex-row items-center justify-between mb-6">
<div class="flex flex-col"> <div class="flex flex-col">
<div class="text-sm font-light text-grey-500">Conversions</div> <div class="text-sm font-light text-grey-500">Conversions</div>
@ -18,16 +18,16 @@
<div class="flex flex-col w-full"> <div class="flex flex-col w-full">
<ul class="list-none"> <ul class="list-none">
<li><a <li><a
class="flex flex-row items-center justify-start h-10 w-full px-2 bg-white hover:bg-grey-100 dark:bg-grey-895 dark:hover:bg-grey-890" class="flex flex-row items-center justify-start h-10 w-full px-2 bg-white hover:bg-grey-100"
href="/">Today</a></li> href="/">Today</a></li>
<li><a <li><a
class="flex flex-row items-center justify-start h-10 w-full px-2 bg-white hover:bg-grey-100 dark:bg-grey-895 dark:hover:bg-grey-890" class="flex flex-row items-center justify-start h-10 w-full px-2 bg-white hover:bg-grey-100"
href="/">This week</a></li> href="/">This week</a></li>
<li><a <li><a
class="flex flex-row items-center justify-start h-10 w-full px-2 bg-white hover:bg-grey-100 dark:bg-grey-895 dark:hover:bg-grey-890" class="flex flex-row items-center justify-start h-10 w-full px-2 bg-white hover:bg-grey-100"
href="/">This month</a></li> href="/">This month</a></li>
<li><a <li><a
class="flex flex-row items-center justify-start h-10 w-full px-2 bg-white hover:bg-grey-100 dark:bg-grey-895 dark:hover:bg-grey-890" class="flex flex-row items-center justify-start h-10 w-full px-2 bg-white hover:bg-grey-100"
href="/">This year</a></li> href="/">This year</a></li>
</ul> </ul>
</div> </div>

View File

@ -0,0 +1,30 @@
<script>
import { _, json } from "svelte-i18n";
import { getlang } from "./datatable_i18n";
import { Grid } from "gridjs";
//
let table;
const datatable = new Grid({
columns: ["Name", "Email", "Phone Number"],
language: getlang($json("datatable")),
sort: true,
search: { enabled: true },
data: [
["John", "john@example.com", "(353) 01 222 3333"],
["Mark", "mark@gmail.com", "(01) 22 888 4444"],
["Eoin", "eoin@gmail.com", "0097 22 654 00033"],
["Sarah", "sarahcdd@gmail.com", "+322 876 1233"],
["Afshin", "afshin@mail.com", "(353) 22 87 8356"],
],
pagination: {
enabled: true,
limit: 2,
summary: false,
},
});
setTimeout(() => {
datatable.render(table);
}, 0);
</script>
<div bind:this={table} />

View File

@ -8,7 +8,7 @@
</div> </div>
</div> </div>
<div <div
class="w-full p-4 mb-4 rounded-lg bg-white border border-grey-100 dark:bg-grey-895 dark:border-grey-890"> class="w-full p-4 mb-4 rounded-lg bg-white border border-grey-100">
<div class="flex flex-row items-center justify-start p-4"> <div class="flex flex-row items-center justify-start p-4">
<div class="flex-shrink-0 w-24"> <div class="flex-shrink-0 w-24">
<img <img

View File

@ -32,7 +32,7 @@
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody class="bg-white divide-y divide-gray-200"> <tbody class="divide-y divide-gray-200">
<tr> <tr>
<td class="px-6 py-4 whitespace-nowrap"> <td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center"> <div class="flex items-center">

View File

@ -0,0 +1,18 @@
<h3 class="text-lg">Tabs</h3>
<div
class="w-full flex sm:border-b sm:border-gray-300 relative flex-col sm:flex-row">
<div
class="flex-1 sm:text-center font-medium pb-3 cursor-pointer hover:text-blue-400 false">
1
</div>
<div
class="flex-1 sm:text-center font-medium pb-3 cursor-pointer hover:text-blue-400 false">
2
</div>
<div
class="flex-1 sm:text-center font-medium pb-3 cursor-pointer hover:text-blue-400 false">
3
</div>
<div
class="hidden sm:block absolute bottom-0 left-0 h-1 bg-blue-400 transition-transform duration-300 ease-out w-1/4 transform translate-x-double" />
</div>

View File

@ -0,0 +1,102 @@
<div>
<div
class="text-xs inline-flex items-center font-bold leading-sm uppercase px-3 py-1 bg-blue-200 text-blue-700 rounded-full">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="feather feather-bell-off mr-2">
<path d="M13.73 21a2 2 0 0 1-3.46 0" />
<path d="M18.63 13A17.89 17.89 0 0 1 18 8" />
<path d="M6.26 6.26A5.86 5.86 0 0 0 6 8c0 7-3 9-3 9h14" />
<path d="M18 8a6 6 0 0 0-9.33-5" />
<line x1="1" y1="1" x2="23" y2="23" />
</svg>
Tag
</div>
<div
class="ml-4 text-xs inline-flex items-center font-bold leading-sm uppercase px-3 py-1 bg-green-200 text-green-700 rounded-full">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="feather feather-arrow-right mr-2">
<line x1="5" y1="12" x2="19" y2="12" />
<polyline points="12 5 19 12 12 19" />
</svg>
Tag
</div>
<div
class="ml-4 text-xs inline-flex items-center font-bold leading-sm uppercase px-3 py-1 bg-orange-200 text-orange-700 rounded-full">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="feather feather-activity mr-2">
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12" />
</svg>
Tag
</div>
<div
class="ml-4 text-xs inline-flex items-center font-bold leading-sm uppercase px-3 py-1 bg-red-200 text-red-700 rounded-full">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="feather feather-archive mr-2">
<polyline points="21 8 21 21 3 21 3 8" />
<rect x="1" y="3" width="22" height="5" />
<line x1="10" y1="12" x2="14" y2="12" />
</svg>
Tag
</div>
<div
class="ml-4 text-xs inline-flex items-center font-bold leading-sm uppercase px-3 py-1 rounded-full bg-white text-gray-700 border">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="feather feather-hard-drive mr-2">
<line x1="22" y1="12" x2="2" y2="12" />
<path
d="M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" />
<line x1="6" y1="16" x2="6.01" y2="16" />
<line x1="10" y1="16" x2="10.01" y2="16" />
</svg>
Tag
</div>
</div>

View File

@ -0,0 +1,312 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import {
RunnerService,
RunnerTeamService,
RunnerOrganizationService,
} from "@odit/lfk-client-js";
import isEmail from "validator/es/lib/isEmail";
import isMobilePhone from "validator/es/lib/isMobilePhone";
import Toastify from "toastify-js";
export let modal_open;
export let current_runners;
$: selected_team = undefined;
let firstname_input;
let lastname_input;
let middlename_input;
let phone_input;
let email_input;
let teams = [];
RunnerTeamService.runnerTeamControllerGetAll().then((val) => {
teams = val;
});
let orgs = [];
RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => {
orgs = val;
});
function focus(el) {
el.focus();
}
$: middlename_input_value = "";
$: phone_input_value = "";
$: email_input_value = "";
$: lastname_input_value = "";
$: firstname_input_value = "";
$: processed_last_submit = true;
$: isPhoneValidOrEmpty =
phone_input_value.includes("+")&&isMobilePhone(
phone_input_value
.replaceAll("(", "")
.replaceAll(")", "")
.replaceAll("-", "")
.replaceAll(" ", "")
) || phone_input_value === "";
$: isEmailValidOrEmpty =
isEmail(email_input_value) || email_input_value === "";
$: isLastnameValid = lastname_input_value.trim().length !== 0;
$: isFirstnameValid = firstname_input_value.trim().length !== 0;
$: createbtnenabled =
isFirstnameValid &&
isLastnameValid &&
isEmailValidOrEmpty &&
isPhoneValidOrEmpty;
(() => {
document.onkeydown = (e) => {
e = e || window.event;
if (e.key === "Escape") {
modal_open = false;
}
if (e.keyCode === 13) {
if (createbtnenabled === true) {
createbtnenabled = false;
submit();
}
}
};
})();
function submit() {
if (processed_last_submit === true) {
processed_last_submit = false;
const toast = Toastify({
text: "Runner is being added...",
duration: -1,
}).showToast();
let postdata = {
group: selected_team,
firstname: firstname_input_value,
lastname: lastname_input_value,
};
if (middlename_input_value) {
postdata.middlename = middlename_input_value;
}
if (phone_input_value) {
postdata.phone = phone_input_value;
}
if (email_input_value) {
postdata.email = email_input_value;
}
RunnerService.runnerControllerPost(postdata)
.then((result) => {
firstname_input_value = "";
lastname_input_value = "";
middlename_input_value = "";
email_input_value = "";
modal_open = false;
//
Toastify({
text: "Runner added",
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
current_runners.push(result);
current_runners = current_runners;
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
//
toast.hideToast();
});
}
}
</script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" />
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span>
<div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
class="h-6 w-6 text-blue-600"
fill="currentColor"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M9.83 8.79L8 9.456V13H6V8.05h.015l5.268-1.918c.244-.093.51-.14.782-.131a2.616 2.616 0 0 1 2.427 1.82c.186.583.356.977.51 1.182A4.992 4.992 0 0 0 19 11v2a6.986 6.986 0 0 1-5.402-2.547l-.581 3.297L15 15.67V23h-2v-5.986l-2.05-1.987-.947 4.298-6.894-1.215.348-1.97 4.924.868L9.83 8.79zM13.5 5.5a2 2 0 1 1 0-4 2 2 0 0 1 0 4z" /></svg>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_('create-a-new-runner')}
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_('please-provide-the-required-information-to-add-a-new-runner')}
</p>
</div>
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6">
<label
for="firstname"
class="block text-sm font-medium text-gray-700">{$_('first-name')}</label>
<input
use:focus
autocomplete="off"
placeholder={$_('first-name')}
class:border-red-500={!isFirstnameValid}
class:focus:border-red-500={!isFirstnameValid}
class:focus:ring-red-500={!isFirstnameValid}
bind:value={firstname_input_value}
bind:this={firstname_input}
type="text"
name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isFirstnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('first-name-is-required')}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="trackname"
class="block text-sm font-medium text-gray-700">{$_('middle-name')}</label>
<input
autocomplete="off"
placeholder={$_('middle-name')}
bind:value={middlename_input_value}
bind:this={middlename_input}
type="text"
name="trackname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
</div>
<div class="col-span-6">
<label
for="lastname"
class="block text-sm font-medium text-gray-700">Last Name</label>
<input
autocomplete="off"
placeholder="Last Name"
class:border-red-500={!isLastnameValid}
class:focus:border-red-500={!isLastnameValid}
class:focus:ring-red-500={!isLastnameValid}
bind:value={lastname_input_value}
bind:this={lastname_input}
type="text"
name="lastname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isLastnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('last-name-is-required')}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="team"
class="block text-sm font-medium text-gray-700">{$_('team')}</label>
<select
name="team"
bind:value={selected_team}
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2">
{#each teams as team}
<option value={team.id}>
{team.parentGroup.name}
&gt;
{team.name}
</option>
{/each}
{#each orgs as org}
<option value={org.id}>{org.name}</option>
{/each}
</select>
</div>
<div class="col-span-6">
<label
for="phone"
class="block text-sm font-medium text-gray-700">Phone</label>
<input
autocomplete="off"
placeholder="Phone"
class:border-red-500={!isPhoneValidOrEmpty}
class:focus:border-red-500={!isPhoneValidOrEmpty}
class:focus:ring-red-500={!isPhoneValidOrEmpty}
bind:value={phone_input_value}
bind:this={phone_input}
type="tel"
name="phone"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isPhoneValidOrEmpty}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{@html $_('the-provided-phone-number-is-invalid-less-than-br-greater-than-please-enter-a-valid-international-number')}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="email"
class="block text-sm font-medium text-gray-700">{$_('e-mail-adress')}</label>
<input
autocomplete="off"
placeholder={$_('e-mail-adress')}
class:border-red-500={!isEmailValidOrEmpty}
class:focus:border-red-500={!isEmailValidOrEmpty}
class:focus:ring-red-500={!isEmailValidOrEmpty}
bind:value={email_input_value}
bind:this={email_input}
type="email"
name="email"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isEmailValidOrEmpty}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('valid-email-is-required')}
</span>
{/if}
</div>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('create')}
</button>
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
{$_('cancel')}
</button>
</div>
</div>
</div>
</div>
{/if}

View File

@ -0,0 +1,364 @@
<script>
import csv from "csvtojson";
import { read as readXlsx, utils as xlsx_utils } from "xlsx";
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import Toastify from "toastify-js";
import {
ImportService,
RunnerTeamService,
RunnerOrganizationService,
} from "@odit/lfk-client-js";
import { createEventDispatcher } from "svelte";
export let opened_from;
export let passed_org;
export let passed_orgs;
export let passed_team;
export let current_runners;
export let import_modal_open;
$: searchvalue = "";
const dispatch = createEventDispatcher();
function cancelModal() {
import_modal_open = false;
dispatch("cancel");
}
(() => {
document.onkeydown = (e) => {
e = e || window.event;
if (e.key === "Escape") {
import_modal_open = false;
}
if (e.keyCode === 13) {
//
}
};
})();
let orgs = [];
RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => {
orgs = val;
});
let teams = [];
RunnerTeamService.runnerTeamControllerGetAll().then((val) => {
teams = val;
});
let selected_org;
$: selected_org_or_team = "";
let files;
let recent_processed = true;
$: json_output = [];
$: {
if (files) {
if (
files[0].type ===
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
) {
const reader = new FileReader();
reader.addEventListener("load", async (e) => {
const data = new Uint8Array(e.target.result);
const out = readXlsx(data, { type: "array" });
json_output = xlsx_utils.sheet_to_json(
out.Sheets[Object.keys(out.Sheets)[0]]
);
});
reader.readAsArrayBuffer(files[0]);
} else {
const reader = new FileReader();
reader.addEventListener("load", async (e) => {
json_output = await csv({
delimiter: [";", ","],
trim: true,
}).fromString(e.target.result);
});
reader.readAsText(files[0]);
}
}
}
function importAction() {
if (recent_processed === true) {
const toast = Toastify({
text: "Runners are being imported...",
duration: -1,
}).showToast();
recent_processed = false;
const mapped = json_output.map(function (runner) {
return {
firstname: runner[`${$_("csv_import__firstname")}`],
middlename: runner[`${$_("csv_import__middlename")}`],
lastname: runner[`${$_("csv_import__lastname")}`],
team:
runner[`${$_("csv_import__team")}`] ||
runner[`${$_("csv_import__class")}`],
};
});
let org = 0;
if (opened_from === "OrgDetail") {
org = passed_org.id;
}
if (opened_from === "OrgOverview") {
org = parseInt(selected_org);
}
if (opened_from === "OrgOverview" || opened_from === "OrgDetail") {
ImportService.importControllerPostOrgsJson(org, mapped)
.then((resp) => {
toast.hideToast();
recent_processed = true;
Toastify({
text: "Import finished",
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
cancelModal();
})
.catch((err) => {
toast.hideToast();
recent_processed = true;
});
}
if (opened_from === "TeamDetail") {
ImportService.importControllerPostTeamsJson(passed_team.id, mapped)
.then((resp) => {
toast.hideToast();
recent_processed = true;
Toastify({
text: "Import finished",
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
cancelModal();
})
.catch((err) => {
toast.hideToast();
recent_processed = true;
});
}
if (opened_from === "RunnerOverview") {
if (selected_org_or_team.includes("ORG_")) {
selected_org_or_team = selected_org_or_team.split("_")[1];
ImportService.importControllerPostOrgsJson(
selected_org_or_team,
mapped
)
.then((resp) => {
current_runners = current_runners.concat(resp);
toast.hideToast();
recent_processed = true;
Toastify({
text: "Import finished",
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
cancelModal();
})
.catch((err) => {
toast.hideToast();
recent_processed = true;
});
}
if (selected_org_or_team.includes("TEAM_")) {
selected_org_or_team = selected_org_or_team.split("_")[1];
ImportService.importControllerPostTeamsJson(
selected_org_or_team,
mapped
)
.then((resp) => {
current_runners = current_runners.concat(resp);
toast.hideToast();
recent_processed = true;
Toastify({
text: "Import finished",
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
cancelModal();
})
.catch((err) => {
toast.hideToast();
recent_processed = true;
});
}
}
}
}
</script>
{#if import_modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
import_modal_open = false;
}}>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" />
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span>
<div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-max sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
class="h-6 w-6 text-blue-600"
fill="currentColor"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M9.83 8.79L8 9.456V13H6V8.05h.015l5.268-1.918c.244-.093.51-.14.782-.131a2.616 2.616 0 0 1 2.427 1.82c.186.583.356.977.51 1.182A4.992 4.992 0 0 0 19 11v2a6.986 6.986 0 0 1-5.402-2.547l-.581 3.297L15 15.67V23h-2v-5.986l-2.05-1.987-.947 4.298-6.894-1.215.348-1.97 4.924.868L9.83 8.79zM13.5 5.5a2 2 0 1 1 0-4 2 2 0 0 1 0 4z" /></svg>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-2 sm:text-left w-full">
<h3 class="text-lg leading-6 font-bold mt-2 text-gray-900">
{$_('runner-import')}
</h3>
</div>
</div>
<div class="mt-5 text-center sm:mt-0 sm:ml-2 sm:text-left w-full">
{#if json_output.length === 0}
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_('please-provide-the-required-csv-xlsx-file')}
</p>
</div>
<div class="overflow-hidden relative mt-4 mb-4">
<input
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
bind:files
type="file" />
</div>
{/if}
{#if json_output.length > 0}
{#if opened_from === 'OrgOverview'}
<p>{$_('import__target-organization')}</p>
<select
name="team"
bind:value={selected_org}
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2">
{#each passed_orgs as o}
<option value={o.id}>{o.name}</option>
{/each}
</select>
<p>{$_('bitte-bestaetige-diese-laeufer-fuer-den-import')}</p>
{/if}
{#if opened_from === 'RunnerOverview'}
<p>Group</p>
<select
name="team"
bind:value={selected_org_or_team}
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2">
{#each teams as team}
<option value="TEAM_{team.id}">
{team.parentGroup.name}
&gt;
{team.name}
</option>
{/each}
{#each orgs as org}
<option value="ORG_{org.id}">{org.name}</option>
{/each}
</select>
{/if}
{#if opened_from === 'OrgDetail'}
<p>
{$_('runnerimport_verify_runners_org', {
values: { org_name: passed_org.name },
})}
</p>
{/if}
<input
type="search"
bind:value={searchvalue}
placeholder={$_('datatable.search')}
aria-label={$_('datatable.search')}
class="p-2 w-full" />
<div class="relative w-full mt-4 mb-4">
<div class="w-full overflow-x-auto">
<table class="divide-y divide-gray-200 w-full">
<thead class="bg-gray-50">
<tr>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('csv_import__firstname')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('csv_import__middlename')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('csv_import__lastname')}
</th>
{#if (opened_from !== 'TeamDetail' && opened_from !== 'RunnerOverview') || (opened_from === 'RunnerOverview' && selected_org_or_team.includes('ORG_'))}
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('csv_import__team')}
</th>
{/if}
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
{#each json_output as runner}
{#if Object.values(runner)
.toString()
.toLowerCase()
.includes(searchvalue)}
<tr>
<td class="px-6 py-4 whitespace-nowrap">
{runner[`${$_('csv_import__firstname')}`]}
</td>
<td class="px-6 py-4 whitespace-nowrap">
{runner[`${$_('csv_import__middlename')}`] || ''}
</td>
<td class="px-6 py-4 whitespace-nowrap">
{runner[`${$_('csv_import__lastname')}`]}
</td>
{#if (opened_from !== 'TeamDetail' && opened_from !== 'RunnerOverview') || (opened_from === 'RunnerOverview' && selected_org_or_team.includes('ORG_'))}
<td class="px-6 py-4 whitespace-nowrap">
{runner[`${$_('csv_import__team')}`] || runner[`${$_('csv_import__class')}`] || '---'}
</td>
{/if}
</tr>
{/if}
{/each}
</tbody>
</table>
</div>
<button
on:click={importAction}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('import-runners')}
</button>
<button
on:click={() => {
json_output = [];
cancelModal();
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('cancel')}
</button>
</div>
{/if}
</div>
</div>
</div>
</div>
</div>
{/if}

View File

@ -0,0 +1,264 @@
<script>
import { _ } from "svelte-i18n";
import lodashIsEqual from "lodash.isequal";
import store from "../../store";
import {
RunnerService,
RunnerTeamService,
RunnerOrganizationService,
} from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import PromiseError from "../base/PromiseError.svelte";
import isEmail from "validator/es/lib/isEmail";
let data_loaded = false;
export let params;
const runner_promise = RunnerService.runnerControllerGetOne(params.runnerid);
$: delete_triggered = false;
$: original_data = {};
$: editable = {};
$: changes_performed = !lodashIsEqual(original_data, editable);
$: isEmailValid =
(editable.email || "") === "" ||
(editable.email && isEmail(editable.email || ""));
$: isFirstnameValid = editable.firstname !== "";
$: isLastnameValid = editable.lastname !== "";
$: save_enabled =
changes_performed && isFirstnameValid && isLastnameValid && isEmailValid;
runner_promise.then((data) => {
data_loaded = true;
original_data = Object.assign(original_data, data);
original_data.group = original_data.group.id;
editable = Object.assign(editable, original_data);
});
let orgs = [];
RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => {
orgs = val;
});
let teams = [];
RunnerTeamService.runnerTeamControllerGetAll().then((val) => {
teams = val;
});
function submit() {
if (data_loaded === true && save_enabled) {
Toastify({
text: $_("updating-runner"),
duration: 2500,
}).showToast();
RunnerService.runnerControllerPut(original_data.id, editable)
.then((resp) => {
Object.assign(original_data, editable);
original_data = editable;
Object.assign(original_data, editable);
Toastify({
text: $_("runner-updated"),
duration: 2500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
})
.catch((err) => {});
} else {
}
}
function deleteRunner() {
RunnerService.runnerControllerRemove(original_data.id, true)
.then((resp) => {
location.replace("./");
})
.catch((err) => {});
}
</script>
{#await runner_promise}
{$_('loading-runners')}
{:then}
<section class="container p-5 select-none">
<div class="flex flex-row mb-4">
<div class="w-full">
<nav class="w-full flex">
<ol class="list-none flex flex-row items-center justify-start">
<li class="flex items-center">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
class="flex-shrink-0 w-5 h-5 mr-2"
fill="currentColor"
width="24"
height="24"><path fill="none" d="M0 0h24v24H0z" />
<path
d="M9.83 8.79L8 9.456V13H6V8.05h.015l5.268-1.918c.244-.093.51-.14.782-.131a2.616 2.616 0 0 1 2.427 1.82c.186.583.356.977.51 1.182A4.992 4.992 0 0 0 19 11v2a6.986 6.986 0 0 1-5.402-2.547l-.581 3.297L15 15.67V23h-2v-5.986l-2.05-1.987-.947 4.298-6.894-1.215.348-1.97 4.924.868L9.83 8.79zM13.5 5.5a2 2 0 1 1 0-4 2 2 0 0 1 0 4z" /></svg>
</li>
<li class="flex items-center">
<a class="mr-2" href="./">{$_('runners')}</a><svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="h-3 w-3 mr-2 stroke-current"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"><line
x1="5"
y1="12"
x2="19"
y2="12" />
<polyline points="12 5 19 12 12 19" /></svg>
</li>
<li class="flex items-center">
<span class="mr-2">{original_data.firstname}
{original_data.middlename || ''}
{original_data.lastname}</span>
</li>
</ol>
</nav>
</div>
</div>
<div class="mb-8 text-3xl font-extrabold leading-tight">
{original_data.firstname}
{original_data.middlename || ''}
{original_data.lastname}
<span data-id="runner_actions_${editable.id}">
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:DELETE')}
{#if delete_triggered}
<button
on:click={deleteRunner}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('confirm-deletion')}</button>
<button
on:click={() => {
delete_triggered = !delete_triggered;
}}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm">{$_('cancel')}</button>
{/if}
{#if !delete_triggered}
<button
on:click={() => {
delete_triggered = true;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('delete-runner')}</button>
{/if}
{/if}
{#if !delete_triggered}
<button
disabled={!save_enabled}
class:opacity-50={!save_enabled}
type="button"
on:click={submit}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('save-changes')}</button>
{/if}
</span>
</div>
<!-- -->
<div class="text-sm w-full">
<label
for="firstname"
class="font-medium text-gray-700">{$_('first-name')}</label>
<input
autocomplete="off"
placeholder={$_('first-name')}
type="text"
class:border-red-500={!isFirstnameValid}
class:focus:border-red-500={!isFirstnameValid}
class:focus:ring-red-500={!isFirstnameValid}
bind:value={editable.firstname}
name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isFirstnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('first-name-is-required')}
</span>
{/if}
</div>
<div class="text-sm w-full">
<label
for="middlename"
class="font-medium text-gray-700">{$_('middle-name')}</label>
<input
autocomplete="off"
placeholder={$_('middle-name')}
type="text"
bind:value={editable.middlename}
name="middlename"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
</div>
<div class="text-sm w-full">
<label
for="lastname"
class="font-medium text-gray-700">{$_('last-name')}</label>
<input
autocomplete="off"
placeholder={$_('last-name')}
type="text"
bind:value={editable.lastname}
class:border-red-500={!isLastnameValid}
class:focus:border-red-500={!isLastnameValid}
class:focus:ring-red-500={!isLastnameValid}
name="lastname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isLastnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('last-name-is-required')}
</span>
{/if}
</div>
<div class="text-sm w-full">
<label
for="email"
class="font-medium text-gray-700">{$_('e-mail-adress')}</label>
<input
autocomplete="off"
placeholder={$_('e-mail-adress')}
type="email"
bind:value={editable.email}
class:border-red-500={!isEmailValid}
class:focus:border-red-500={!isEmailValid}
class:focus:ring-red-500={!isEmailValid}
name="email"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isEmailValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('valid-email-is-required')}
</span>
{/if}
</div>
<div class="text-sm w-full">
<label for="phone" class="font-medium text-gray-700">{$_('phone')}</label>
<input
autocomplete="off"
placeholder={$_('phone')}
type="tel"
bind:value={editable.phone}
name="phone"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
</div>
<div class="text-sm w-full">
<span class="font-medium text-gray-700">{$_('group')}</span>
<select
bind:value={editable.group}
name="team"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2">
{#each teams as team}
<option value={team.id}>
{team.parentGroup.name}
&gt;
{team.name}
</option>
{/each}
{#each orgs as org}
<option value={org.id}>{org.name}</option>
{/each}
</select>
</div>
<div class="text-sm w-full">
<span class="font-medium text-gray-700">{$_('distance')}</span>
<br />
<span class="text-gray-700">{original_data.distance} km</span>
</div>
</section>
{:catch error}
<PromiseError {error} />
{/await}

View File

@ -0,0 +1,49 @@
<script>
import { _ } from "svelte-i18n";
import store from "../../store";
import AddRunnerModal from "./AddRunnerModal.svelte";
import ImportRunnerModal from "./ImportRunnerModal.svelte";
import RunnersOverview from "./RunnersOverview.svelte";
$: current_runners = [];
export let modal_open = false;
export let import_modal_open = false;
</script>
<section class="container p-5">
<span class="mb-1 text-3xl font-extrabold leading-tight">
{$_('runners')}
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:CREATE')}
<button
on:click={() => {
modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
Läufer hinzufügen
</button>
<button
on:click={() => {
import_modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
Läufer importieren
</button>
{/if}
</span>
<RunnersOverview bind:current_runners />
</section>
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:CREATE')}
<AddRunnerModal bind:current_runners bind:modal_open />
<ImportRunnerModal
on:cancelDelete={(event) => {
import_modal_open = false;
}}
passed_team={{}}
passed_orgs={[]}
passed_org={{}}
bind:current_runners
opened_from="RunnerOverview"
bind:import_modal_open />
{/if}

View File

@ -0,0 +1,16 @@
<script>
import { _ } from "svelte-i18n";
// import AddUserModal from "./AddUserModal.svelte";
import runners_empty from "./runners_empty.svg";
// let modal_open = false;
</script>
<div class="text-center items-center justify-center">
<p class="mb-16 text-lg text-gray-500">
<img class="w-full h-44" src={runners_empty} alt="" />
<span class="font-bold">There are no runners added yet.</span><br />
<span>Add your first runner</span>
</p>
</div>
<!-- <AddUserModal bind:modal_open /> -->

View File

@ -0,0 +1,187 @@
<script>
import { _ } from "svelte-i18n";
import { RunnerService,RunnerTeamService,
RunnerOrganizationService, } from "@odit/lfk-client-js";
import store from "../../store";
import RunnersEmptyState from "./RunnersEmptyState.svelte";
import Select from 'svelte-select';
$: searchvalue = "";
$: active_deletes = [];
export let current_runners = [];
const runners_promise = RunnerService.runnerControllerGetAll().then((val) => {
current_runners = val;
});
$: selectedFilter_teams = null;
$: selectedFilter = null;
$: filter__teams = selectedFilter_teams||[];
$: filter__orgs = selectedFilter||[];
$:filterGroupIDs=filter__teams.concat(filter__orgs).map(i=>i.value)
$: teams = [];
$: orgs = [];
$:mappedteams=teams.map(function(g){
return {value:g.id,label:g.parentGroup.name+" > "+g.name}
})
$:selectgroups=(orgs.map(function(g){
return {value:g.id,label:g.name}
})).concat(mappedteams)
RunnerTeamService.runnerTeamControllerGetAll().then((val) => {
teams = val;
});
RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => {
orgs = val;
});
function should_display_based_on_id(id) {
if(searchvalue.toString().slice(-1)==="*"){
return id.toString().startsWith(searchvalue.replace("*",""))
}
return id.toString()===searchvalue;
}
</script>
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:GET')}
{#await runners_promise}
<div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert">
<p class="font-bold">runners are being loaded...</p>
<p class="text-sm">{$_('this-might-take-a-moment')}</p>
</div>
{:then}
{#if current_runners.length === 0}
<RunnersEmptyState />
{:else}
<input
type="search"
bind:value={searchvalue}
placeholder={$_('datatable.search')}
aria-label={$_('datatable.search')}
class="gridjs-input gridjs-search-input mb-4" />
<div class="block mb-1">
<label for="country" class="text-sm font-medium text-gray-700">Filter by Organization/ Team</label>
<Select on:select={(event)=>{
selectedFilter=event.detail
}} selectedValue={selectedFilter} placeholder="Filter by Organization/ Team" containerClasses="mt-1 py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" items={selectgroups} isMulti={true}></Select>
</div>
<div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
<table class="divide-y divide-gray-200 w-full">
<thead class="bg-gray-50">
<tr>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('name')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('contact-information')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('group')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('distance-in-km')}
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">{$_('action')}</span>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
{#each current_runners as runner}
{#if runner.firstname.toLowerCase().includes(searchvalue.toLowerCase())||runner.middlename.toLowerCase().includes(searchvalue.toLowerCase())||runner.lastname.toLowerCase().includes(searchvalue.toLowerCase())||should_display_based_on_id(runner.id)}
{#if filterGroupIDs.includes(runner.group.id)||filterGroupIDs.includes(runner.group.parentGroup?.id)||filterGroupIDs.length===0}
<tr data-rowid="user_{runner.id}" data-groupid={runner.group.id}>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{runner.firstname}
{runner.middlename || ''}
{runner.lastname}
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
{#if runner.email}
<div class="text-sm text-gray-500">{runner.email}</div>
{/if}
{#if runner.phone}
<div class="text-sm text-gray-500">{runner.phone}</div>
{/if}
</td>
<td class="px-6 py-4 whitespace-nowrap">
{#if runner.group.responseType === 'RUNNERTEAM'}
<a
href="../teams/{runner.group.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{runner.group.name}</a>
{/if}
{#if runner.group.responseType === 'RUNNERORGANIZATION'}
<a
href="../orgs/{runner.group.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{runner.group.name}</a>
{/if}
</td>
<td class="px-6 py-4 whitespace-nowrap">{runner.distance}</td>
{#if active_deletes[runner.id] === true}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
on:click={() => {
active_deletes[runner.id] = false;
}}
tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">Cancel
Delete</button>
<button
on:click={() => {
RunnerService.runnerControllerRemove(runner.id, true)
.then((resp) => {
current_runners = current_runners.filter((obj) => obj.id !== runner.id);
})
.catch((err) => {
// error deleting user
});
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('confirm-delete')}</button>
</td>
{:else}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<a
href="./{runner.id}"
class="text-indigo-600 hover:text-indigo-900">Edit</a>
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:DELETE')}
<button
on:click={() => {
active_deletes[runner.id] = true;
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">{$_('delete')}</button>
{/if}
</td>
{/if}
</tr>
{/if}
{/if}
{/each}
</tbody>
</table>
</div>
{/if}
{:catch error}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b>
{error}
</span>
</div>
{/await}
{/if}

View File

@ -0,0 +1 @@
<svg data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 854.63 686"><path fill="#3f3d56" d="M0 600h821v9.053H0zM0 676.947h821V686H0z"/><path d="M750.178 608.328c-.49-.802-12.06-20.12-16.071-60.234-3.68-36.802-1.313-98.836 30.858-185.367 60.947-163.928-14.046-296.194-14.812-297.512l3.7-2.146c.194.334 19.545 34.057 30.977 87.755a382.846 382.846 0 01-15.856 213.394c-60.844 163.648-15.61 241.118-15.146 241.882z" fill="#3f3d56"/><circle cx="726.346" cy="27.795" r="27.795" fill="#3f3d56"/><circle cx="814.007" cy="130.422" r="27.795" fill="#3f3d56"/><circle cx="754.141" cy="198.841" r="27.795" fill="#dfe5ee"/><circle cx="826.835" cy="256.569" r="27.795" fill="#dfe5ee"/><circle cx="732.76" cy="346.368" r="27.795" fill="#3f3d56"/><path d="M766.97 609.35s-27.796-68.418 55.59-119.731zM732.786 608.11s-12.65-72.758-110.557-72.134z" fill="#3f3d56"/><circle cx="136.5" cy="387.5" r="41" fill="#a0616a"/><path d="M143.928 336.504c-7.84-1.925-16.272-2.247-23.868.49-7.824 2.817-14.263 8.708-19 15.542s-7.918 14.613-10.767 22.425a78.442 78.442 0 00-3.845 13.14 44.992 44.992 0 008.167 34.325c-1.2-3.166 1.822-6.617 5.138-7.303s6.693.474 9.963 1.353a61.559 61.559 0 0017.68 2.078c2.284-.065 4.679-.295 6.578-1.565 5.952-3.98 2.802-14.263 7.754-19.434 1.767-1.845 4.29-2.708 6.567-3.865 8.2-4.167 13.604-12.576 16.156-21.413 1.599-5.54 5.567-21.45 2.185-26.598-3.057-4.653-17.516-7.9-22.708-9.175z" fill="#2f2e41"/><path d="M248.066 342.291a24.396 24.396 0 00-18.677 1.041c-10.695 5.016-29.608 17.167-28.889 40.168 1 32 12 53 12 53l-2 65-10 118s-49 40-30 50 51-48 51-48l25-67 9-65s15-68 0-98c0 0 17.328-40.17-7.434-49.209zM367.5 445.5s-17 72 43 92 139 54 139 54l10 17 23-31-4-24-39-11s-73-56-105-60l4-37zM527.307 387.717L548.5 422.5s70 40 88 67 61 56 61 56l-19 45s-54-63-77-69-125-88-125-88z" fill="#a0616a"/><path d="M338.568 265.819c-7.805 1.226-15.617 2.454-23.33 4.174-17.917 3.995-35.147 10.616-51.966 17.974a608.405 608.405 0 00-68.447 35.267 132.255 132.255 0 00-16.97 11.523c-3.559 2.964-6.91 6.363-8.82 10.584a33.406 33.406 0 00-2.33 9.221c-1.55 11.12-1.477 22.595 1.535 33.41s9.124 20.955 18.119 27.674a29.88 29.88 0 007.84 4.348 45.675 45.675 0 0011.675 1.957c12.617 1.003 25.798 1.923 37.474-2.964 7.42-3.105 13.701-8.354 20.528-12.607 28.913-18.008 65.646-17.241 97.26-29.92 3.074-1.233 6.204-2.67 8.354-5.189a22.578 22.578 0 003.62-7.187l11.99-33.188c2.533-7.01 5.067-14.023 7.193-21.167 2.224-7.472 3.996-15.07 5.766-22.661 1.524-6.534 3.093-13.376 2.507-20.13-.864-9.962-3.166-10.367-12.316-8.925q-24.839 3.915-49.682 7.806z" fill="#dfe5ee"/><path d="M218.066 342.291a24.396 24.396 0 00-18.677 1.041c-10.695 5.016-29.608 17.167-28.889 40.168 1 32 12 53 12 53l-2 65-10 118s-49 40-30 50 51-48 51-48l25-67 9-65s15-68 0-98c0 0 17.328-40.17-7.434-49.209z" fill="#a0616a"/><path d="M391.66 257.544S480.5 249.5 476.5 304.5s-34 150-34 150l-77 4s-10-65 4-87z" fill="#2f2e41"/><path d="M453.5 275.5s22 14 34 42 48 83 48 83l-59 42-44-45zM559.5 587.5s-19 7-6 21a126.61 126.61 0 0120 29s-4 26 13 25 24-36 20-48-9-69-9-69-35 3-34 8 6 30-4 34zM678.5 567.5s-18 9-13 17 21 31 21 31-6 30 11 33 27-36 24-44-3-39-3-39 1-31-9-31-27 4-27 4 4 28-4 29z" fill="#2f2e41"/><path fill="#dfe5ee" d="M96 617.871h29v50.612H96z"/></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -0,0 +1,181 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import {
RunnerOrganizationService,
RunnerTeamService,
} from "@odit/lfk-client-js";
import Toastify from "toastify-js";
export let modal_open;
export let current_teams;
let teamname_input_dom;
function focus(el) {
el.focus();
}
$: teamname = "";
$: processed_last_submit = true;
$: isTeamNameValid = teamname.trim().length !== 0;
$: createbtnenabled = isTeamNameValid;
(function () {
document.onkeydown = function (e) {
e = e || window.event;
if (e.key === "Escape") {
modal_open = false;
}
if (e.keyCode === 13) {
if (createbtnenabled === true) {
createbtnenabled = false;
submit();
}
}
};
})();
$: parentGroup = undefined;
$: orgs = [];
RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => {
orgs = val;
});
function submit() {
if (processed_last_submit === true) {
processed_last_submit = false;
const toast = Toastify({
text: "Team is being added...",
duration: -1,
}).showToast();
RunnerTeamService.runnerTeamControllerPost({
parentGroup,
name: teamname,
})
.then((result) => {
teamname = "";
modal_open = false;
//
Toastify({
text: "Team added",
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
current_teams.push(result);
current_teams = current_teams;
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
//
toast.hideToast();
});
}
}
</script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" />
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span>
<div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<svg
class="h-6 w-6 text-blue-600"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"><path
fill="currentColor"
d="M96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm448 0c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm32 32h-64c-17.6 0-33.5 7.1-45.1 18.6 40.3 22.1 68.9 62 75.1 109.4h66c17.7 0 32-14.3 32-32v-32c0-35.3-28.7-64-64-64zm-256 0c61.9 0 112-50.1 112-112S381.9 32 320 32 208 82.1 208 144s50.1 112 112 112zm76.8 32h-8.3c-20.8 10-43.9 16-68.5 16s-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48v-28.8c0-63.6-51.6-115.2-115.2-115.2zm-223.7-13.4C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
Create a new team
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
Please provide the required information to add a new team.
</p>
</div>
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6">
<label
for="firstname"
class="block text-sm font-medium text-gray-700">{$_('team-name')}</label>
<input
use:focus
autocomplete="off"
placeholder={$_('team-name')}
class:border-red-500={!isTeamNameValid}
class:focus:border-red-500={!isTeamNameValid}
class:focus:ring-red-500={!isTeamNameValid}
bind:value={teamname}
bind:this={teamname_input_dom}
type="text"
name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isTeamNameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
team name is required
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="firstname"
class="block text-sm font-medium text-gray-700">{$_('organization')}</label>
<select
bind:value={parentGroup}
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2">
{#each orgs as t}
<option value={t.id}>{t.name}</option>
{/each}
</select>
</div>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('create')}
</button>
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
{$_('cancel')}
</button>
</div>
</div>
</div>
</div>
{/if}

View File

@ -0,0 +1,97 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { RunnerTeamService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import { createEventDispatcher } from "svelte";
export let modal_open;
export let delete_team;
const dispatch = createEventDispatcher();
function cancelDelete() {
modal_open = false;
dispatch("cancelDelete", { id: delete_team.id });
}
function deleteTeam() {
RunnerTeamService.runnerTeamControllerRemove(delete_team.id, true)
.then((resp) => {
Toastify({
text: "Team deleted",
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
location.replace("./");
})
.catch((err) => {
//
});
}
</script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={cancelDelete}>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" />
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span>
<div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<svg
class="h-6 w-6 text-blue-600"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"><path
fill="currentColor"
d="M96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm448 0c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm32 32h-64c-17.6 0-33.5 7.1-45.1 18.6 40.3 22.1 68.9 62 75.1 109.4h66c17.7 0 32-14.3 32-32v-32c0-35.3-28.7-64-64-64zm-256 0c61.9 0 112-50.1 112-112S381.9 32 320 32 208 82.1 208 144s50.1 112 112 112zm76.8 32h-8.3c-20.8 10-43.9 16-68.5 16s-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48v-28.8c0-63.6-51.6-115.2-115.2-115.2zm-223.7-13.4C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
Attention!
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
Do you want to delete the team
{delete_team.name}?<br />All associated runners will be
deleted too!
</p>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
on:click={deleteTeam}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">
Confirm, delete team and associated runners.
</button>
<button
on:click={cancelDelete}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
Cancel, keep team
</button>
</div>
</div>
</div>
</div>
{/if}

View File

@ -0,0 +1,262 @@
<script>
import {
GroupContactService,
RunnerOrganizationService,
RunnerTeamService,
} from "@odit/lfk-client-js";
import { _ } from "svelte-i18n";
import Toastify from "toastify-js";
import store from "../../store";
import ImportRunnerModal from "../runners/ImportRunnerModal.svelte";
import PromiseError from "../base/PromiseError.svelte";
import ConfirmTeamDeletion from "./ConfirmTeamDeletion.svelte";
let [teamdata, original, delete_team, orgs, contacts, modal_open] = [
{},
{},
{},
[],
[],
false,
];
export let params;
export let import_modal_open = false;
$: delete_triggered = false;
$: save_enabled = !data_changed;
$: data_loaded = false;
$: data_changed = JSON.stringify(teamdata) === JSON.stringify(original);
//
const promise = RunnerTeamService.runnerTeamControllerGetOne(
params.teamid
).then((value) => {
data_loaded = true;
if (value.contact) {
if (value.contact !== "null") {
value.contact = value.contact.id;
}
}
teamdata = Object.assign(teamdata, value);
original = Object.assign(original, value);
});
RunnerOrganizationService.runnerOrganizationControllerGetAll().then((val) => {
orgs = val;
});
GroupContactService.groupContactControllerGetAll().then((val) => {
contacts = val;
});
function deleteTeam() {
RunnerTeamService.runnerTeamControllerRemove(original.id, false)
.then((resp) => {
Toastify({
text: "Organization deleted",
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
location.replace("./");
})
.catch((err) => {
modal_open = true;
delete_team = original;
});
}
function submit() {
if (data_loaded === true && save_enabled) {
Toastify({
text: "updating team",
duration: 2500,
}).showToast();
teamdata.parentGroup = teamdata.parentGroup.id;
let postdata = teamdata;
postdata.contact = postdata.contact === "null" ? null : postdata.contact;
RunnerTeamService.runnerTeamControllerPut(original.id, postdata)
.then((resp) => {
Object.assign(original, teamdata);
original = teamdata;
Object.assign(original, teamdata);
//
Toastify({
text: "updated team",
duration: 2500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
})
.catch((err) => {});
}
}
</script>
<ImportRunnerModal
current_runners={[]}
on:cancelDelete={(event) => {
import_modal_open = false;
}}
passed_team={teamdata}
passed_orgs={[]}
passed_org={{}}
opened_from="TeamDetail"
bind:import_modal_open />
<ConfirmTeamDeletion bind:modal_open bind:delete_team />
{#if data_loaded}
<section class="container p-5">
<div class="mb-8 text-3xl font-extrabold leading-tight">
{original.name}
<span data-id="org_actions_${teamdata.id}">
{#if store.state.jwtinfo.userdetails.permissions.includes('RUNNER:IMPORT')}
<button
on:click={() => {
import_modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('import-runners')}
</button>
{/if}
{#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:DELETE')}
{#if delete_triggered}
<button
on:click={deleteTeam}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('confirm-delete')}</button>
<button
on:click={() => {
delete_triggered = !delete_triggered;
}}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm">{$_('cancel')}</button>
{/if}
{#if !delete_triggered}
<button
on:click={() => {
delete_triggered = true;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('delete-team')}</button>
{/if}
{/if}
{#if !delete_triggered}
<button
on:click={submit}
disabled={!save_enabled}
class:opacity-50={!save_enabled}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('save-changes')}</button>
{/if}
</span>
</div>
<div class="flex flex-row mb-4">
<div class="w-full">
<nav class="w-full flex">
<ol class="list-none flex flex-row items-center justify-start">
<li class="mr-2 flex items-center">
<svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="h-3 w-3 stroke-current"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"><path
d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
<polyline points="9 22 9 12 15 12 15 22" /></svg>
</li>
<li class="flex items-center">
<a class="mr-2" href="/">Home</a><svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="h-3 w-3 mr-2 stroke-current"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"><line
x1="5"
y1="12"
x2="19"
y2="12" />
<polyline points="12 5 19 12 12 19" /></svg>
</li>
<li class="mr-2 flex items-center">
<svg
class="flex-shrink-0 w-5 h-5 mr-2"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"><path
fill="currentColor"
d="M96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm448 0c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm32 32h-64c-17.6 0-33.5 7.1-45.1 18.6 40.3 22.1 68.9 62 75.1 109.4h66c17.7 0 32-14.3 32-32v-32c0-35.3-28.7-64-64-64zm-256 0c61.9 0 112-50.1 112-112S381.9 32 320 32 208 82.1 208 144s50.1 112 112 112zm76.8 32h-8.3c-20.8 10-43.9 16-68.5 16s-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48v-28.8c0-63.6-51.6-115.2-115.2-115.2zm-223.7-13.4C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg>
</li>
<li class="flex items-center">
<a class="mr-2" href="./">Teams</a><svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="h-3 w-3 mr-2 stroke-current"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"><line
x1="5"
y1="12"
x2="19"
y2="12" />
<polyline points="12 5 19 12 12 19" /></svg>
</li>
<li class="flex items-center">
<span class="mr-2">Team-Details #{params.teamid}</span>
</li>
</ol>
</nav>
</div>
</div>
<div class="text-sm w-full">
<label for="name" class="font-medium text-gray-700">Name</label>
<input
autocomplete="off"
placeholder="Name"
type="text"
bind:value={teamdata.name}
name="name"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
</div>
<div class="text-sm w-full">
<label
for="contact"
class="font-medium text-gray-700">{$_('contact')}</label>
<select
name="contact"
bind:value={teamdata.contact}
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2">
<option value="null">no contact</option>
{#each contacts as c}
<option value={c.id}>
{c.firstname}
{c.middlename || ''}
{c.lastname}
</option>
{/each}
</select>
</div>
<div class="text-sm w-full">
<label for="org" class="font-medium text-gray-700">Parent Organization</label>
<select
name="org"
bind:value={teamdata.parentGroup}
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2">
{#each orgs as o}
<option value={o.id}>{o.name}</option>
{/each}
</select>
</div>
</section>
{:else}
{#await promise}
team detail is being loaded...
{:catch error}
<PromiseError />
{/await}
{/if}

View File

@ -0,0 +1,30 @@
<script>
import { _ } from "svelte-i18n";
import store from "../../store";
import AddTeamModal from "./AddTeamModal.svelte";
export let modal_open = false;
import TeamsOverview from "./TeamsOverview.svelte";
let current_teams=[];
</script>
<section class="container p-5">
<span class="mb-1 text-3xl font-extrabold leading-tight">
{$_('teams')}
{#if store.state.jwtinfo.userdetails.permissions.includes('USER:CREATE')}
<button
on:click={() => {
modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
Create Team
</button>
{/if}
</span>
<p class="mb-8 text-lg text-gray-500">everything is more fun together 🏃‍♂️🏃‍♀️🏃‍♂️</p>
<TeamsOverview bind:current_teams />
</section>
{#if store.state.jwtinfo.userdetails.permissions.includes('USER:CREATE')}
<AddTeamModal bind:current_teams bind:modal_open />
{/if}

View File

@ -0,0 +1,17 @@
<script>
import { _ } from "svelte-i18n";
import AddTeamModal from "./AddTeamModal.svelte";
import team_empty from "./team_empty.svg";
let modal_open = false;
let current_teams = [];
</script>
<div class="text-center items-center justify-center">
<p class="mb-16 text-lg text-gray-500">
<img class="w-full h-44" src={team_empty} alt="" />
<span class="font-bold">There are no teams added yet.</span><br />
<span>Add your first team</span>
</p>
</div>
<AddTeamModal bind:modal_open bind:current_teams />

View File

@ -0,0 +1,178 @@
<script>
import { t, _ } from "svelte-i18n";
import Toastify from "toastify-js";
import { RunnerTeamService } from "@odit/lfk-client-js";
const teams_promise = RunnerTeamService.runnerTeamControllerGetAll();
import store, { users as usersstore } from "../../store.js";
import TeamsEmptyState from "./TeamsEmptyState.svelte";
import ConfirmTeamDeletion from "./ConfirmTeamDeletion.svelte";
$: searchvalue = "";
$: active_deletes = [];
export let current_teams = [];
let modal_open = false;
let delete_team = {};
usersstore.subscribe((val) => {
current_teams = val;
});
teams_promise.then((data) => {
usersstore.set(data);
});
</script>
<ConfirmTeamDeletion
on:cancelDelete={(event) => {
modal_open = false;
active_deletes[event.detail.id] = false;
}}
bind:modal_open
bind:delete_team />
{#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:GET')}
{#await teams_promise}
<div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert">
<p class="font-bold">teams are being loaded...</p>
<p class="text-sm">{$_('this-might-take-a-moment')}</p>
</div>
{:then}
{#if current_teams.length === 0}
<TeamsEmptyState />
{:else}
<input
type="search"
bind:value={searchvalue}
placeholder={$_('datatable.search')}
aria-label={$_('datatable.search')}
class="gridjs-input gridjs-search-input mb-4" />
<div
class="shadow border-b border-gray-200 sm:rounded-lg overflow-x-scroll">
<table class="divide-y divide-gray-200 w-full">
<thead class="bg-gray-50">
<tr>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Name
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{$_('organization')}
</th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Contact
</th>
<th scope="col" class="relative px-6 py-3">
<span class="sr-only">Action</span>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
{#each current_teams as t}
{#if Object.values(t)
.toString()
.toLowerCase()
.includes(searchvalue)}
<tr data-rowid="team_{t.id}">
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{t.name}
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{#if t.parentGroup}
<a
href="../orgs/{t.parentGroup.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{t.parentGroup.name}</a>
{:else}no organization specified{/if}
</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">
{#if t.contact}
<a
href="../contacts/{t.contact.id}"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{t.contact.firstname}
{t.contact.middlename || ''}
{t.contact.lastname}</a>
{:else}no contact specified{/if}
</div>
</div>
</div>
</td>
{#if active_deletes[t.id] === true}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
on:click={() => {
active_deletes[t.id] = false;
}}
tabindex="0"
class="ml-4 text-indigo-600 hover:text-indigo-900 cursor-pointer">Cancel
Delete</button>
<button
on:click={() => {
RunnerTeamService.runnerTeamControllerRemove(t.id, false)
.then((resp) => {
current_teams = current_teams.filter((obj) => obj.id !== t.id);
Toastify({
text: 'Organization deleted',
duration: 500,
backgroundColor:
'linear-gradient(to right, #00b09b, #96c93d)',
}).showToast();
})
.catch((err) => {
modal_open = true;
delete_team = t;
});
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">Confirm
Delete</button>
</td>
{:else}
<td
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<a
href="./{t.id}"
class="text-indigo-600 hover:text-indigo-900">Edit</a>
{#if store.state.jwtinfo.userdetails.permissions.includes('TEAM:DELETE')}
<button
on:click={() => {
active_deletes[t.id] = true;
}}
tabindex="0"
class="ml-4 text-red-600 hover:text-red-900 cursor-pointer">Delete</button>
{/if}
</td>
{/if}
</tr>
{/if}
{/each}
</tbody>
</table>
</div>
{/if}
{:catch error}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b>
{error}
</span>
</div>
{/await}
{/if}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 52 KiB

View File

@ -0,0 +1,224 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { tracks as tracksstore } from "../../store.js";
import { TrackService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import "toastify-js/src/toastify.css";
export let modal_open;
let trackname_input;
function focus(el) {
el.focus();
}
$: trackname_input_value = "";
$: track_min_duration = 0;
$: tracklength = 0;
$: processed_last_submit = true;
$: smart_track_min_duration_placeholder = parseInt(tracklength || 0) * 0.369;
$: isTracknameValid = trackname_input_value.trim().length === 0;
$: isTracklengthValid = tracklength <= 0;
$: trackMintimevalid = track_min_duration >= 0;
$: createbtnenabled = !isTracknameValid && !isTracklengthValid;
(function () {
document.onkeydown = function (e) {
e = e || window.event;
if (e.key === "Escape") {
modal_open = false;
}
if (e.keyCode === 13) {
if (createbtnenabled === true) {
createbtnenabled = false;
submit();
}
}
};
})();
function submit() {
if (processed_last_submit === true) {
processed_last_submit = false;
const toast = Toastify({
text: $_("track-is-being-added"),
duration: -1,
}).showToast();
TrackService.trackControllerPost({
distance: parseInt(tracklength),
name: trackname_input_value,
minimumLapTime: track_min_duration,
})
.then((result) => {
trackname_input_value = "";
track_min_duration = 0;
tracklength = 0;
modal_open = false;
//
Toastify({
text: $_("track-added"),
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
let storeval = [];
tracksstore.subscribe((val) => {
storeval = val;
});
storeval.push(result);
tracksstore.set(storeval);
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
//
toast.hideToast();
});
}
}
</script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" />
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span>
<div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<svg
class="h-6 w-6 text-blue-600"
fill="none"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"><path
fill="currentColor"
d="M635.7 167.2L556.1 31.7c-8.8-15-28.3-20.1-43.5-11.5l-69 39.1L503.3 161c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L416 75l-55.2 31.3 27.9 47.4c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L333.2 122 278 153.3 337.8 255c2.2 3.7.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9l-59.7-101.7-55.2 31.3 27.9 47.4c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9l-27.9-47.5-55.2 31.3 59.7 101.7c2.2 3.7.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L84.9 262.9l-69 39.1C.7 310.7-4.6 329.8 4.2 344.8l79.6 135.6c8.8 15 28.3 20.1 43.5 11.5L624.1 210c15.2-8.6 20.4-27.8 11.6-42.8z" /></svg>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{$_('create-a-new-track')}
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
{$_('please-provide-the-required-information-to-add-a-new-track')}
</p>
</div>
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6">
<label
for="trackname"
class="block text-sm font-medium text-gray-700">{$_('track-name')}</label>
<input
use:focus
autocomplete="off"
placeholder={$_('track-name')}
class:border-red-500={isTracknameValid}
class:focus:border-red-500={isTracknameValid}
class:focus:ring-red-500={isTracknameValid}
bind:value={trackname_input_value}
bind:this={trackname_input}
type="text"
name="trackname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if isTracknameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
Track name must not be empty
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="track_length_m"
class="block text-sm font-medium text-gray-700">{$_('track-length-in-m')}</label>
<div class="mt-1 flex rounded-md shadow-sm">
<input
autocomplete="off"
class:border-red-500={isTracklengthValid}
class:focus:border-red-500={isTracklengthValid}
class:focus:ring-red-500={isTracklengthValid}
bind:value={tracklength}
type="number"
name="track_length_m"
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 p-2"
placeholder="1000" />
<span
class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500 text-sm">m</span>
</div>
{#if isTracklengthValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
Track length must be greater than 0
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="track_min_duration"
class="block text-sm font-medium text-gray-700">{$_('minimum-lap-time-in-s')}</label>
<div class="mt-1 flex rounded-md shadow-sm">
<input
autocomplete="off"
class:border-red-500={!trackMintimevalid}
class:focus:border-red-500={!trackMintimevalid}
class:focus:ring-red-500={!trackMintimevalid}
bind:value={track_min_duration}
type="number"
name="track_min_duration"
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 p-2"
placeholder={smart_track_min_duration_placeholder} />
<span
class="inline-flex items-center px-3 rounded-r-md border border-gray-300 bg-gray-50 text-gray-500 text-sm">s</span>
</div>
{#if !trackMintimevalid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
minimum lap time must be a positive number or 0
</span>
{/if}
</div>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
Create
</button>
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
Cancel
</button>
</div>
</div>
</div>
</div>
{/if}

View File

@ -0,0 +1,186 @@
<script>
import { _, json } from "svelte-i18n";
import Toastify from "toastify-js";
import TracksEmptyState from "./TracksEmptyState.svelte";
import { TrackService } from "@odit/lfk-client-js";
const tracks_promise = TrackService.trackControllerGetAll();
import { getlang } from "../base/datatable_i18n";
import { Grid, html } from "gridjs";
import { tracks as tracksstore } from "../../store.js";
$: trackscache = [];
$: blocked = [];
let table;
let datatable;
let datatable_inited = false;
tracksstore.subscribe((val) => {
trackscache = val;
setTimeout(() => {
if (val.length > 0) {
renderdatatable();
}
}, 100);
});
tracks_promise.then((data) => {
tracksstore.set(data);
});
window.track__edit_cancel = () => renderdatatable();
window.track__edit_save = () => {
const trackid = parseInt(window.event.target.getAttribute("data-trackid"));
if (blocked.includes(trackid)) {
//
} else {
blocked.push(trackid);
const elem = document.querySelector(
`[data-id="triggered_table_actions_${trackid}"]`
).parentNode.parentNode.parentNode;
Toastify({
text: "Track is being updated...",
duration: 500,
}).showToast();
TrackService.trackControllerPut(trackid, {
id: trackid,
name: elem.childNodes[0].childNodes[0].value,
distance: parseInt(elem.childNodes[1].childNodes[0].value),
minimumLapTime: parseInt(elem.childNodes[2].childNodes[0].value),
})
.then((r) => {
Toastify({
text: "Track was updated!",
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
duration: 1000,
}).showToast();
blocked = blocked.filter((e) => e !== trackid);
document
.querySelector(`[data-id="default_table_actions_${trackid}"]`)
.classList.remove("hidden");
document
.querySelector(`[data-id="triggered_table_actions_${trackid}"]`)
.classList.add("hidden");
//
elem.childNodes[0].innerHTML = `<td data-column-id="trackName" class="gridjs-td">${elem.childNodes[0].childNodes[0].value}</td>`;
elem.childNodes[1].innerHTML = `<td data-column-id="trackName" class="gridjs-td">${elem.childNodes[1].childNodes[0].value}</td>`;
elem.childNodes[2].innerHTML = `<td data-column-id="trackName" class="gridjs-td">${elem.childNodes[2].childNodes[0].value}</td>`;
})
.catch((err) => {
});
}
};
window.track__delete_handler = () => {
const trackid = parseInt(window.event.target.getAttribute("data-trackid"));
document
.querySelector(`[data-id="default_table_actions_${trackid}"]`)
.classList.add("hidden");
document
.querySelector(`[data-id="deleteconfirmation_table_actions_${trackid}"]`)
.classList.remove("hidden");
};
window.track__delete_cancel = () => {
const trackid = parseInt(window.event.target.getAttribute("data-trackid"));
document
.querySelector(`[data-id="default_table_actions_${trackid}"]`)
.classList.remove("hidden");
document
.querySelector(`[data-id="deleteconfirmation_table_actions_${trackid}"]`)
.classList.add("hidden");
};
window.track__delete_confirm = () => {
const trackid = parseInt(window.event.target.getAttribute("data-trackid"));
TrackService.trackControllerRemove(trackid)
.then(() => {
const newStoreVal = trackscache.filter((obj) => obj.id !== trackid);
tracksstore.set(newStoreVal);
renderdatatable();
})
.catch((err) => {
});
};
window.track__edit_handler = () => {
const trackid = parseInt(window.event.target.getAttribute("data-trackid"));
document
.querySelector(`[data-id="default_table_actions_${trackid}"]`)
.classList.add("hidden");
document
.querySelector(`[data-id="triggered_table_actions_${trackid}"]`)
.classList.remove("hidden");
const elem = document.querySelector(
`[data-id="triggered_table_actions_${trackid}"]`
).parentNode.parentNode.parentNode;
const trackname = elem.childNodes[0].textContent;
const tracklength = parseInt(elem.childNodes[1].textContent);
const trackmintime = parseInt(elem.childNodes[2].textContent);
elem.childNodes[0].innerHTML = `<input type="text" value="${trackname}" name="trackname" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2">`;
elem.childNodes[1].innerHTML = `<input type="text" value="${tracklength}" name="tracklength" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2">`;
elem.childNodes[2].innerHTML = `<input type="text" value="${trackmintime}" name="trackmintime" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2">`;
};
//
function renderdatatable() {
let tabledata = [];
trackscache.forEach((track) => {
tabledata.push([
track.name,
track.distance,
track.minimumLapTime || 0,
html(`
<div class="hidden" data-id="triggered_table_actions_${track.id}">
<button class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-green-400 text-base font-medium text-white sm:w-auto sm:text-sm" data-trackid="${track.id}" onclick="track__edit_save()">Save</button>
<button class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-500 text-base font-medium text-white sm:w-auto sm:text-sm" data-trackid="${track.id}" onclick="track__edit_cancel()">Cancel</button>
</div>
<div data-id="default_table_actions_${track.id}">
<button class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-400 text-base font-medium text-white sm:w-auto sm:text-sm" data-trackid="${track.id}" onclick="track__edit_handler()">Edit</button>
<button class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-500 text-base font-medium text-white sm:w-auto sm:text-sm" data-trackid="${track.id}" onclick="track__delete_handler()">Delete</button>
</div>
<div class="hidden" data-id="deleteconfirmation_table_actions_${track.id}">
<button class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-400 text-base font-medium text-white sm:w-auto sm:text-sm" data-trackid="${track.id}" onclick="track__delete_cancel()">Cancel</button>
<button class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-500 text-base font-medium text-white sm:w-auto sm:text-sm" data-trackid="${track.id}" onclick="track__delete_confirm()">Confirm</button>
</div>
`),
]);
});
if (datatable_inited === false) {
datatable = new Grid({
columns: [
$_("track-name"),
$_("track-length-in-m"),
$_("minimum-lap-time-in-s"),
$_("action"),
],
language: getlang($json("datatable")),
sort: true,
search: { enabled: true },
data: tabledata,
pagination: {
enabled: true,
limit: 25,
summary: false,
},
}).render(table);
datatable_inited = true;
} else {
datatable.updateConfig({ data: tabledata }).forceRender();
}
}
</script>
{#if trackscache.length > 0}
<div bind:this={table} />
{/if}
{#await tracks_promise}
<div
class="bg-teal-lightest border-t-4 border-teal rounded-b text-teal-darkest px-4 py-3 shadow-md my-2"
role="alert">
<p class="font-bold">{$_('track-data-is-being-loaded')}</p>
<p class="text-sm">{$_('this-might-take-a-moment')}</p>
</div>
{:then}
{#if trackscache.length === 0}
<TracksEmptyState />
{/if}
{:catch error}
<div class="text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-500">
<span class="inline-block align-middle mr-8">
<b class="capitalize">{$_('general_promise_error')}</b>
{error}
</span>
</div>
{/await}

View File

@ -0,0 +1,260 @@
<script>
import { _ } from "svelte-i18n";
import AddTrackModal from "./AddTrackModal.svelte";
let modal_open = false;
</script>
<div class="text-center items-center justify-center">
<svg
class="mx-auto"
style="height:15rem;"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 997 808"><defs>
<linearGradient
id="a"
x1="570.56"
y1="454"
x2="570.56"
y2="63.07"
gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="gray" stop-opacity=".25" />
<stop offset=".54" stop-color="gray" stop-opacity=".12" />
<stop offset="1" stop-color="gray" stop-opacity=".1" />
</linearGradient>
</defs>
<path
d="M260.64.9c93 12.73 168.51 101.25 252.3 156.49 18.67 12.31 38.24 23.15 58.93 26.36 68.54 10.64 131.74-63.09 200.6-57.07 55.81 4.88 103.14 64 123.22 133.54s17 147.32 5.85 220.32c-17.46 114-56.14 226.15-123.47 300.3-11.11 12.24-23.52 23.69-37.79 26.47-23.78 4.69-45.83-15.46-66.41-32.12-92.88-75.22-210.3-94.32-314-51.08-35.12 14.64-68.71 36-104.65 46.54-69.21 20.26-147.53-5.34-194.52-76s-54.63-186.8-9.86-260c18.69-30.55 44.45-52.12 64.07-81.6 49.3-74 31.1-148.82 25-235.6C133.9 31.94 206.31-6.53 260.64.9z"
fill="#4299e1"
opacity=".1" />
<g opacity=".2">
<path
d="M683.38 545s27.51 27.51 12 68.33S722 722.03 722 722.03l-1.29-.21c-56.5-10-84.57-74.59-53.15-122.59 11.66-17.81 21.25-38.75 15.82-54.23z"
fill="#4299e1" />
<path
d="M683.38 545.5s27.51 27.51 12 68.33S722 722.53 722 722.53l-1.29-.21c-56.5-10-84.57-74.59-53.15-122.59 11.66-17.86 21.25-38.79 15.82-54.23z"
opacity=".1" />
<path
d="M683.38 545s15.53 33.72 0 61.23-2.66 104.71 38.6 115.8"
fill="none"
stroke="#535461"
stroke-miterlimit="10" />
<path
d="M634.39 656.3s31.39-5 34.25 23.13 60.36 32 60.36 32l-1.08.72c-47.48 31.17-92.86 19.85-86-21.53 2.53-15.35 2.32-30.81-7.53-34.32z"
fill="#4299e1" />
<path
d="M634.39 656.3s24.37 5.35 22.36 27.5 36.18 48.71 72.25 27.66"
fill="none"
stroke="#535461"
stroke-miterlimit="10" />
</g>
<g opacity=".2">
<path
d="M357.1 501.68s22.86 22.86 10 56.77 22.12 90.32 22.12 90.32l-1.07-.18c-46.94-8.29-70.27-62-44.17-101.86 9.66-14.83 17.63-32.22 13.12-45.05z"
fill="#4299e1" />
<path
d="M357.1 502.06s22.86 22.86 10 56.77 22.12 90.32 22.12 90.32l-1.12-.15c-46.94-8.29-70.27-62-44.17-101.86 9.71-14.86 17.68-32.25 13.17-45.08z"
opacity=".1" />
<path
d="M357.1 501.68s12.9 28 0 50.87-2.21 87 32.07 96.22"
fill="none"
stroke="#535461"
stroke-miterlimit="10" />
<path
d="M316.39 594.13s26.11-4.13 28.46 19.22S395 640 395 640l-.9.6c-39.45 25.9-77.16 16.5-71.47-17.89 2.11-12.83 1.94-25.71-6.24-28.58z"
fill="#4299e1" />
<path
d="M316.39 594.13s20.24 4.44 18.58 22.85 30.06 40.48 60 23"
fill="none"
stroke="#535461"
stroke-miterlimit="10" />
</g>
<g opacity=".2">
<path
d="M261.1 357.68s22.86 22.86 10 56.77 22.12 90.32 22.12 90.32l-1.07-.18c-46.94-8.29-70.27-62-44.17-101.86 9.66-14.83 17.63-32.22 13.12-45.05z"
fill="#4299e1" />
<path
d="M261.1 358.06s22.86 22.86 10 56.77 22.12 90.32 22.12 90.32l-1.12-.15c-46.94-8.29-70.27-62-44.17-101.86 9.71-14.86 17.68-32.25 13.17-45.08z"
opacity=".1" />
<path
d="M261.1 357.68s12.9 28 0 50.87-2.21 87 32.07 96.22"
fill="none"
stroke="#535461"
stroke-miterlimit="10" />
<path
d="M220.39 450.13s26.11-4.13 28.46 19.22S299 496 299 496l-.9.6c-39.45 25.9-77.16 16.5-71.47-17.89 2.11-12.83 1.94-25.71-6.24-28.58z"
fill="#4299e1" />
<path
d="M220.39 450.13s20.24 4.44 18.58 22.85 30.06 40.48 60 23"
fill="none"
stroke="#535461"
stroke-miterlimit="10" />
</g>
<path
fill="#dfe5ee"
d="M14.76 592.38v-13.63l279.29-78.33 49.95-5.68v12.49l-278.15 99.9-51.09-14.75z" />
<path
opacity=".05"
d="M14.76 592.38v-13.63l279.29-78.33 49.95-5.68v12.49l-278.15 99.9-51.09-14.75z" />
<path
fill="#dfe5ee"
d="M14.76 578.75l279.29-91.96 48.82 7.95-277.02 98.77-51.09-14.76z" />
<path
fill="#dfe5ee"
d="M57.9 370.99h18.17v211.17H57.9zM275.88 322.17h18.17v185.06h-18.17z" />
<path
fill="#dfe5ee"
d="M339.46 264.27v56.77L112.67 371.7l-26.83 6-66.54 14.86L0 386.88v-65.84l102.91-19.62 25.61-4.88 187.1-35.67 23.84 3.4z" />
<path
opacity=".05"
d="M339.46 264.27v56.77L112.67 371.7l-26.83 6-66.54 14.86L0 386.88v-65.84l102.91-19.62 25.61-4.88 187.1-35.67 23.84 3.4z" />
<path
opacity=".05"
d="M76.07 370.99v11.16l-18.17 4.06v-15.22h18.17zM294.05 322.17v11.29l-18.17 4.05v-15.34h18.17z" />
<path
fill="#dfe5ee"
d="M339.46 264.27v56.77l-92.08 20.57-26.78 5.98-107.93 24.11-26.83 6-66.54 14.86 1.14-64.71 92.47-18.43 25.87-5.15h.01L244.5 283.2l25.82-5.15 69.14-13.78z" />
<path
fill="#4299e1"
d="M138.79 304.27l-26.12 67.43-26.83 6 27.07-68.28-10-8 25.61-4.88 10.26 7.73h.01zM271.62 279.03l-24.24 62.58-26.78 5.98 25.14-63.41-1.24-.98-8.76-7.02 25.61-4.88 8.97 6.75 1.29.98h.01z" />
<path
opacity=".05"
d="M61.88 578.47l18.16-4.26v4.26l-18.16 4.26v-4.26zM278.72 503.54l18.16-4.26v4.26l-18.16 4.26v-4.26z" />
<path
d="M761.23 394.51c-28.95 15.25-58.92-2-58.92-2a19.07 19.07 0 01-1.61 2.52c-.37-.28-.75-.57-1.13-.88-12.44-10.17-26.21-34.18-26.21-34.18l-26.83-50.31a78.52 78.52 0 01-7.37-19.19c-4.42-19.18-18.53-20-18.53-20H604.1c-8 0-19.56 1.68-31 4a2.1 2.1 0 00.57-1.14l-.55.11 6.14-6s17.57-7.12 19.64-22.37l9.3-9.15s.3-.8.72-2.21l3.09-3.89c13.46-16.85 14.43-17.13 14.43-17.13l40.81-24.15s14.65-2.08 8.54-43.05-2-46-2-46 11.1-41.05-10.18-35.94-11.66 40.2-11.66 40.2l1.48 7.75a74.2 74.2 0 01-.3 29.35c-2.21 10.45-4.68 23.36-4.06 26.44 1.09 5.37-11.2 12.17-11.2 12.17l-6.95 4.06a35.17 35.17 0 01-24.39 4.16l-.37-.56L602 184a12.05 12.05 0 00-.3 1.3L600 184v.1c0-.36 0-.7.08-1.08a53.88 53.88 0 012.69-11.28c.11-.34.25-.7.37-1A30.72 30.72 0 00632 140.29a30 30 0 00-1.52-9.44 6.51 6.51 0 002.38 0c3.74-.6 7.71-2.52 9.65-5.83a10.7 10.7 0 00-.79-11.72 11.71 11.71 0 00-1.6-1.67c-3.55-4-9.49-5-14.21-7.69-4.49-2.53-7.9-6.57-12.08-9.58-7.9-5.71-18-7.43-27.64-9-4.14-.67-8.35-1.34-12.51-.79-5.26.7-10.1 3.33-14.22 6.62a12.8 12.8 0 00-3.59 3.95c-1.53 3-.86 6.52-1.13 9.84-.66 8.08-7 14.66-14 18.95-4.3 2.63-9 4.73-12.61 8.26s-5.74 9.1-3.49 13.56c1.19 2.36 3.54 4.33 3.56 7a7.84 7.84 0 01-1 3.34 48.67 48.67 0 01-9.59 13.5c-14.6-3.52-30.36 4.22-30.36 4.22l-47.55 2s-13.44-6.1-31 31.52-23.78 39.66-23.78 39.66-32 28.47-11.37 35.59 32-27.46 32-27.46L417 233.84s25.84-24.41 27.91-29.49 14.47-5.08 14.47-5.08l50.41 2a9.59 9.59 0 01.12 1.23c.12 5.64-5.17 10-10.49 12.09-3.36 1.34-7.2 2.35-9.25 5.29s-1.56 7.21.5 10.21a13.42 13.42 0 002.19 2.43 20.53 20.53 0 007.72 5 35.81 35.81 0 007.51 2.08 76 76 0 01-4.25 12.6l2 .86-.66 1.76c-2.82 7.49-8.63 22.27-13.77 31.21-.05 0-9.34 7.88-16.55 15-5.51 5.42-18.79 57.6 20.07 58.54l8.87 6.54h.18c7.89 11.14 33.94 29.44 33.94 29.44l-46.07 2.72a57.66 57.66 0 00-6.85.83q-1.13.2-2.26.45c-8.12 1.78-17.11 1.15-21.93.58-3.19-6-18-3.56-18-3.56s-2.07 30.51 1 47.79 14.47 6.1 14.47 6.1c-2.07-15.25 15-26.47 15-26.47 2.17-1.09 4.74-2.41 7.76-4 9.58-5.14 55.15 2.72 69.24 5.32a47.65 47.65 0 009.41.8c40.39-.66 29.9-20.36 29.9-20.36l-8.13-16a82.22 82.22 0 01-3.56-8.18c-7.18-19-18.34-29.46-26.89-35.06.46-.58.92-1.16 1.37-1.77 0 0-.68-.16-1.71-.47 7.22-3.69 12-6.64 12-6.64s47.55-28.47 45.49-21.35 10.34 34.57 33.08 53.89c.66.56 1.3 1.13 1.94 1.71l.62.57 1.27 1.2.69.67 1.18 1.18.66.68 1.25 1.34.5.54q.85.94 1.68 1.9l.36.43q.64.76 1.27 1.52l.57.71 1 1.28.58.75 1 1.31.52.69 1.16 1.61.28.38q.71 1 1.39 2l.34.51 1 1.51.42.65.89 1.4.39.62.93 1.53.27.44q1.21 2 2.3 4l.12.22.95 1.72.15.28.91 1.72.06.12a151.46 151.46 0 016.87 15.07 26.4 26.4 0 01-3.53.6c-6.2 27.46 10.34 27.46 10.34 27.46s61-9.15 83.74-32.54-4.08-21.44-4.08-21.44z"
transform="translate(-101.5 -46)"
fill="url(#a)" />
<path
d="M438.5 296.5s25.14 6.52 37.68 40.22a81.56 81.56 0 003.45 8l7.87 15.78s10.15 19.37-28.92 20a45.36 45.36 0 01-9.1-.79c-13.63-2.55-57.71-10.28-67-5.23a172.92 172.92 0 01-16 8l-11-12 4-16s13.21 2.48 24.62-.07a55.86 55.86 0 018.81-1.26l44.59-2.65s-35-25-35-34 36-20 36-20z"
fill="#f8bdc5" />
<path
d="M363.5 357.5s-11 17.94 11.49 21c0 0-16.49 11-14.49 26 0 0-11 11-14-6s-1-47-1-47 18-3 18 6z"
opacity=".05" />
<path
d="M438.5 296.5s25.14 6.52 37.68 40.22a81.56 81.56 0 003.45 8l7.87 15.78s10.15 19.37-28.92 20a45.36 45.36 0 01-9.1-.79c-13.63-2.55-57.71-10.28-67-5.23a172.92 172.92 0 01-16 8l-11-12 4-16s13.21 2.48 24.62-.07a55.86 55.86 0 018.81-1.26l44.59-2.65s-35-25-35-34 36-20 36-20z"
opacity=".05" />
<path
d="M360.5 372.5l4-16s13.21 2.48 24.62-.07a55.86 55.86 0 018.81-1.26l44.57-2.67s-35-25-35-34 36-20 36-20l.27.08a32.74 32.74 0 00-5.27-2.08s-36 11-36 20 35 34 35 34l-44.56 2.67a55.86 55.86 0 00-8.81 1.26c-11.41 2.55-24.62.07-24.62.07l-4 16 11 12s.74-.3 2.26-1z"
opacity=".05" />
<path
d="M363.5 357.5s-11 17.94 11.49 21c0 0-16.49 11-14.49 26 0 0-11 11-14-6s-1-47-1-47 18-3 18 6z"
fill="#ffd97b" />
<path
fill="#dfe5ee"
d="M333.76 647.38v-13.63l279.29-78.33 49.95-5.68v12.49l-278.15 99.9-51.09-14.75z" />
<path
opacity=".05"
d="M333.76 647.38v-13.63l279.29-78.33 49.95-5.68v12.49l-278.15 99.9-51.09-14.75z" />
<path
fill="#dfe5ee"
d="M333.76 633.75l279.29-91.96 48.82 7.95-277.02 98.77-51.09-14.76z" />
<path
fill="#dfe5ee"
d="M376.9 425.99h18.17v211.17H376.9zM594.88 377.17h18.17v185.06h-18.17z" />
<path
fill="#dfe5ee"
d="M658.46 319.27v56.77L431.67 426.7l-26.83 6-66.54 14.86-19.3-5.68v-65.84l102.9-19.62 25.62-4.88 187.1-35.67 23.84 3.4z" />
<path
opacity=".05"
d="M658.46 319.27v56.77L431.67 426.7l-26.83 6-66.54 14.86-19.3-5.68v-65.84l102.9-19.62 25.62-4.88 187.1-35.67 23.84 3.4z" />
<path
opacity=".05"
d="M395.07 425.99v11.16l-18.17 4.06v-15.22h18.17zM613.05 377.17v11.29l-18.17 4.05v-15.34h18.17z" />
<path
fill="#dfe5ee"
d="M658.46 319.27v56.77l-92.08 20.57-26.78 5.98-107.93 24.11-26.83 6-66.54 14.86 1.14-64.71 92.47-18.43 25.87-5.15h.01L563.5 338.2l25.82-5.15 69.14-13.78z" />
<path
fill="#4299e1"
d="M457.79 359.27l-26.12 67.43-26.83 6 27.07-68.28-10.01-8 25.62-4.88 10.26 7.73h.01zM590.62 334.03l-24.24 62.58-26.78 5.98 25.14-63.41-1.24-.98-8.76-7.02 25.61-4.88 8.97 6.75 1.29.98h.01z" />
<path
opacity=".05"
d="M380.88 633.47l18.16-4.26v4.26l-18.16 4.26v-4.26zM597.72 558.54l18.16-4.26v4.26l-18.16 4.25v-4.25z" />
<path
d="M562.63 146.9l-39.48 23.75s-.94.28-14 16.85l-4.69 6c-9.92 12.75-30.54 17-30.54 17l-13.08-20.12-2.76-24.44 2.9-9.87 2.3-7.82 20.12 2.88 8.1 4.37 10-10c.78.21 1.57.4 2.36.55a33.55 33.55 0 0023.59-4.05l6.72-4s11.88-6.69 10.83-12c-.6-3 1.79-15.72 3.93-26a74.16 74.16 0 00.29-28.86l-1.43-7.62S538.5 29 559.07 24s9.85 35.34 9.85 35.34-3.94 4.94 2 45.23-8.29 42.33-8.29 42.33z"
fill="#f8bdc5" />
<path
d="M497.55 141.54c-1.12 10.58 3.95 12 3.95 12l3 12v13l-9 15h-22l-19-9v-42l-3-10a38.7 38.7 0 005-.65c9.65-1.93 15.12-7.29 18.2-13.11 4.8-9.11 3.8-19.28 3.8-19.28s40-11 26 20c-1.86 4.13-3.28 7.76-4.35 11a53.72 53.72 0 00-2.6 11.04zM472.09 229.33a2.08 2.08 0 01-.59 1.17 8.65 8.65 0 00-1.29 2c-4.68 8.83-13.71 40-13.71 40l-49-18-.71-.18-19.29-4.82c5.47-3.91 14.61-27.36 18.33-37.42 1-2.81 1.67-4.58 1.67-4.58l52 5s13.48 11.8 12.59 16.83z"
fill="#f8bdc5" />
<path
d="M451.5 129.5s-15 6-30 0-33 3-33 3l-46 2s-13-6-30 31-23 39-23 39-31 28-11 35 31-27 31-27l11-21s25-24 27-29 14-5 14-5l50 2-.31 14.62 15.31 13.38 14-12 11-22z"
fill="#f8bdc5" />
<path
d="M504.5 119.5c-1.86 4.13-3.28 7.76-4.35 11h-1.65a29.92 29.92 0 01-23.81-11.76c4.81-9.07 3.81-19.24 3.81-19.24s40-11 26 20z"
opacity=".1" />
<circle cx="498.5" cy="99.5" r="30" fill="#f8bdc5" />
<path
d="M472.09 229.33a2.08 2.08 0 01-.59 1.17 8.65 8.65 0 00-1.29 2c-17.79-.78-56.72-17.11-64.38-20.38 1-2.81 1.67-4.58 1.67-4.58l52 5s13.48 11.76 12.59 16.79zM509.19 187.5l-4.69 6c-9.92 12.75-30.54 17-30.54 17l-13.08-20.12-2.76-24.44L459 142c2.79 13 10.59 40.12 22.5 42.5 19.91 4 18.48-24.53 17.5-36.5a16.2 16.2 0 01.5-5.5l4 3 .36.55c9.53 14.8 7.39 32.42 5.33 41.45z"
opacity=".1" />
<path
d="M456.5 127.5h-10s10 64-37 46c0 0 4 15-5 36 0 0 48 21 67 21l6-6s17-7 19-22l9-9s10-27-4-48l-4-3a16.16 16.16 0 00-.5 5.5c1 12 2.41 40.48-17.5 36.5-25-5-23-57-23-57z"
fill="#eb6576" />
<path
d="M388.5 309.5l16 12s26 8 47-21c0 0-13-3-11-10s-52 19-52 19z"
fill="#5e52ad" />
<path
d="M388.5 309.5l16 12s26 8 47-21c0 0-13-3-11-10s-52 19-52 19z"
opacity=".05" />
<path
d="M472.09 230.33a2.08 2.08 0 01-.59 1.17c-4 4-15 42-15 42l-49-18-.71-.18a70.21 70.21 0 0112-6.06c3.41-1.32 6.84-2.72 9.81-4.06a44.92 44.92 0 006.91-3.7c4.19-3 20.28-7.8 36.58-11.17z"
opacity=".1" />
<path
d="M585.5 372.5l-15 4s-.34-1-1-2.74c-3.2-8.14-13.86-32.61-32-48.26-22-19-34-46-32-53s-44 21-44 21-33.35 20.75-55.39 20.78a23.28 23.28 0 01-9.61-1.78c-8.5-3.83-11.94-10.54-13.09-17a29.72 29.72 0 016-23.31 68.38 68.38 0 0129.4-21.93c3.41-1.32 6.84-2.72 9.81-4.06a44.92 44.92 0 006.91-3.7c7-5 47-15 66-15h16s13.64.8 17.92 19.66a78 78 0 007.1 18.84l25.95 49.5s13.32 23.61 25.35 33.61c3.43 2.86 6.76 4.61 9.65 4.39 13-1-18 19-18 19z"
fill="#f8bdc5" />
<path
d="M538.66 84.52A10.67 10.67 0 00537.9 73c-3.36-4.37-9.43-5.37-14.22-8.11-4.35-2.49-7.64-6.46-11.68-9.42-7.64-5.61-17.38-7.31-26.73-8.84-4-.66-8.08-1.32-12.1-.77-5.09.69-9.77 3.28-13.76 6.51a12.52 12.52 0 00-3.47 3.88c-1.48 2.92-.83 6.42-1.09 9.68-.64 7.95-6.78 14.42-13.55 18.63-4.16 2.59-8.74 4.65-12.2 8.12s-5.55 8.95-3.37 13.33c1.16 2.32 3.43 4.26 3.44 6.86a7.8 7.8 0 01-.94 3.29 47.87 47.87 0 01-9.54 13.55c-3.54 3.52-7.77 6.68-9.61 11.33-1.77 4.47-1 9.57.63 14.1.76 2.16 1.71 4.32 1.75 6.6.11 5.55-5 9.81-10.15 11.89-3.25 1.31-7 2.31-8.95 5.2s-1.51 7.09.48 10 5.2 4.86 8.5 6.19a31.16 31.16 0 0019.38 1.58 23.74 23.74 0 0015.15-11.78c3-5.84 3.2-13 7.36-18.1 7.33-8.91 22.79-6.66 30.44-15.31 6.14-6.95 4.47-17.48 4-26.74a82.37 82.37 0 01.64-15.45c.5-3.64 1.42-7.55 4.28-9.85 2.31-1.86 5.43-2.28 8.37-2.64 6.14-.77 13.21-2.26 16-7.79 1.16-2.3 1.51-5.2 3.6-6.7 2.89-2.08 6.94 0 9.46 2.5 2.78 2.77 5.11 6.18 9.34 5.49 3.59-.56 7.43-2.45 9.3-5.71z"
opacity=".1" />
<path
d="M537.58 83.44a10.67 10.67 0 00-.77-11.52c-3.36-4.37-9.43-5.37-14.22-8.11-4.35-2.49-7.64-6.46-11.68-9.42-7.64-5.61-17.38-7.31-26.73-8.84-4-.66-8.08-1.32-12.1-.77-5.09.69-9.77 3.28-13.76 6.51a12.52 12.52 0 00-3.47 3.88c-1.48 2.92-.83 6.42-1.09 9.68-.64 7.95-6.78 14.42-13.55 18.63-4.16 2.59-8.74 4.65-12.2 8.12s-5.55 8.95-3.37 13.33c1.16 2.32 3.43 4.26 3.44 6.86a7.8 7.8 0 01-.94 3.29 47.87 47.87 0 01-9.54 13.55c-3.54 3.52-7.77 6.68-9.61 11.33-1.77 4.47-1 9.57.63 14.1.76 2.16 1.71 4.32 1.75 6.6.11 5.55-5 9.81-10.15 11.89-3.25 1.31-7 2.31-8.95 5.2s-1.51 7.09.48 10 5.2 4.86 8.5 6.19a31.16 31.16 0 0019.38 1.58 23.74 23.74 0 0015.15-11.78c3-5.84 3.2-13 7.36-18.1 7.33-8.91 22.79-6.66 30.44-15.31 6.14-6.95 4.47-17.48 4-26.74a82.37 82.37 0 01.64-15.45c.5-3.64 1.42-7.55 4.28-9.85 2.31-1.86 5.43-2.28 8.37-2.64 6.14-.77 13.21-2.26 16-7.79 1.16-2.3 1.51-5.2 3.6-6.7 2.89-2.08 6.94 0 9.46 2.5 2.78 2.77 5.11 6.18 9.34 5.49 3.59-.57 7.44-2.46 9.31-5.71z"
fill="#6e464e" />
<path d="M434 202s11 9 25 8" opacity=".1" />
<path
d="M605.92 353.68c-5.74 5.64-18.42 13.82-18.42 13.82l-15 4s-11-32-33-51-34-46-32-53-44 21-44 21-33.35 20.75-55.39 20.78a23.28 23.28 0 01-9.61-1.78c-8.5-3.83-11.94-10.54-13.09-17a28.68 28.68 0 01.82-13.31 29.19 29.19 0 00-2.82 18.31c1.15 6.46 4.59 13.17 13.09 17a23.28 23.28 0 009.61 1.78c22 0 55.39-20.78 55.39-20.78s46-28 44-21 10 34 32 53 33 51 33 51l15-4s25.89-16.71 20.42-18.82z"
opacity=".05" />
<path
d="M429.5 246.5l-1.7.79a61.63 61.63 0 00-28.9 27.46c-13.4 26.08 4.31 37.83 7.21 39.53a23.28 23.28 0 01-9.61-1.78c-8.5-3.83-11.94-10.54-13.09-17a29.72 29.72 0 016-23.31 68.38 68.38 0 0129.4-21.93c3.41-1.32 6.84-2.72 9.81-4.06z"
opacity=".1" />
<path
d="M427.5 246.5s-21-7.46-35-3.73c0 0-9 7.73-16 14.73-5.7 5.7-20 64 28 57 0 0-22.16-11.42-7.6-39.75a61.62 61.62 0 0128.9-27.46z"
fill="#5e52ad" />
<path
d="M585.5 372.5l-15 4s-.34-1-1-2.74c3.49-.92 8-3 8-7.26 0 0 11.45-10.69 16.35-17.39 3.43 2.86 6.76 4.61 9.65 4.39 13-1-18 19-18 19z"
opacity=".1" />
<path
d="M578.5 367.5s15-14 18-20c0 0 29 17 57 2 0 0 26-2 4 21s-81 32-81 32-16 0-10-27c0 0 12-1 12-8z"
fill="#ffd97b" />
<path
fill="#dfe5ee"
d="M667.76 717.38v-13.63l279.29-78.33 49.95-5.68v12.49l-278.15 99.9-51.09-14.75z" />
<path
opacity=".05"
d="M667.76 717.38v-13.63l279.29-78.33 49.95-5.68v12.49l-278.15 99.9-51.09-14.75z" />
<path
fill="#dfe5ee"
d="M667.76 703.75l279.29-91.96 48.82 7.95-277.02 98.77-51.09-14.76z" />
<path
fill="#dfe5ee"
d="M710.9 495.99h18.17v211.17H710.9zM928.88 447.17h18.17v185.06h-18.17z" />
<path
fill="#dfe5ee"
d="M992.46 389.27v56.77L765.67 496.7l-26.83 6-66.54 14.86-19.3-5.68v-65.84l102.9-19.62 25.62-4.88 187.1-35.67 23.84 3.4z" />
<path
opacity=".05"
d="M992.46 389.27v56.77L765.67 496.7l-26.83 6-66.54 14.86-19.3-5.68v-65.84l102.9-19.62 25.62-4.88 187.1-35.67 23.84 3.4z" />
<path
opacity=".05"
d="M729.07 495.99v11.16l-18.17 4.06v-15.22h18.17zM947.05 447.17v11.29l-18.17 4.05v-15.34h18.17z" />
<path
fill="#dfe5ee"
d="M992.46 389.27v56.77l-92.08 20.57-26.78 5.98-107.93 24.11-26.83 6-66.54 14.86 1.14-64.71 92.47-18.43 25.87-5.15h.01L897.5 408.2l25.82-5.15 69.14-13.78z" />
<path
fill="#4299e1"
d="M791.79 429.27l-26.12 67.43-26.83 6 27.07-68.28-10.01-8 25.62-4.88 10.26 7.73h.01zM924.62 404.03l-24.24 62.58-26.78 5.98 25.14-63.41-1.24-.98-8.76-7.02 25.61-4.88 8.97 6.75 1.29.98h.01z" />
<path
opacity=".05"
d="M714.88 703.47l18.16-4.26v4.26l-18.16 4.26v-4.26zM931.72 628.54l18.16-4.26v4.26l-18.16 4.25v-4.25z" /></svg>
<p class="mb-16 text-lg text-gray-500">
<span class="font-bold">{$_('no-tracks-added-yet')}</span><br />
<span>{$_('add-your-first-track')}</span>
</p>
</div>
<AddTrackModal bind:modal_open />

View File

@ -0,0 +1,25 @@
<script>
import { _ } from "svelte-i18n";
import AddTrackModal from "./AddTrackModal.svelte";
let modal_open = false;
import Tracks from "./Tracks.svelte";
</script>
<section class="container p-5">
<span class="mb-1 text-3xl font-extrabold leading-tight">
Tracks
<button
on:click={() => {
modal_open = true;
}}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
Create Track
</button>
</span>
<p class="mb-8 text-lg text-gray-500">
configure the tracks & minimum lap times
</p>
<Tracks />
</section>
<AddTrackModal bind:modal_open />

View File

@ -0,0 +1,271 @@
<script>
import { _ } from "svelte-i18n";
import { clickOutside } from "../base/outsideclick";
import { focusTrap } from "svelte-focus-trap";
import { UserService } from "@odit/lfk-client-js";
import isEmail from "validator/es/lib/isEmail";
import Toastify from "toastify-js";
export let modal_open;
export let current_users;
let firstname_input;
let lastname_input;
let middlename_input;
let username_input;
let password_input;
let email_input;
function focus(el) {
el.focus();
}
$: username_input_value = "";
$: middlename_input_value = "";
$: password_input_value = "";
$: email_input_value = "";
$: lastname_input_value = "";
$: firstname_input_value = "";
$: processed_last_submit = true;
$: isPasswordValid = password_input_value.trim().length !== 0;
$: isEmailValid = isEmail(email_input_value);
$: isLastnameValid = lastname_input_value.trim().length !== 0;
$: isFirstnameValid = firstname_input_value.trim().length !== 0;
$: createbtnenabled =
isFirstnameValid && isLastnameValid && isPasswordValid && isEmailValid;
(function () {
document.onkeydown = function (e) {
e = e || window.event;
if (e.key === "Escape") {
modal_open = false;
}
if (e.keyCode === 13) {
if (createbtnenabled === true) {
createbtnenabled = false;
submit();
}
}
};
})();
function submit() {
if (processed_last_submit === true) {
processed_last_submit = false;
const toast = Toastify({
text: "User is being added...",
duration: -1,
}).showToast();
UserService.userControllerPost({
firstname: firstname_input_value,
lastname: lastname_input_value,
middlename: middlename_input_value,
password: password_input_value,
email: email_input_value,
username: username_input_value,
})
.then((result) => {
firstname_input_value = "";
lastname_input_value = "";
middlename_input_value = "";
email_input_value = "";
username_input_value = "";
modal_open = false;
//
Toastify({
text: "User added",
duration: 500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
current_users.push(result);
current_users = current_users;
})
.catch((err) => {
//
})
.finally(() => {
processed_last_submit = true;
//
toast.hideToast();
});
}
}
</script>
{#if modal_open}
<div
class="fixed z-10 inset-0 overflow-y-auto"
use:focusTrap
use:clickOutside
on:click_outside={() => {
modal_open = false;
}}>
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div
class="absolute inset-0 bg-gray-500 opacity-75"
data-id="modal_backdrop" />
</div>
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span>
<div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<svg
class="h-6 w-6 text-blue-600"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"><path
fill="currentColor"
d="M610.5 341.3c2.6-14.1 2.6-28.5 0-42.6l25.8-14.9c3-1.7 4.3-5.2 3.3-8.5-6.7-21.6-18.2-41.2-33.2-57.4-2.3-2.5-6-3.1-9-1.4l-25.8 14.9c-10.9-9.3-23.4-16.5-36.9-21.3v-29.8c0-3.4-2.4-6.4-5.7-7.1-22.3-5-45-4.8-66.2 0-3.3.7-5.7 3.7-5.7 7.1v29.8c-13.5 4.8-26 12-36.9 21.3l-25.8-14.9c-2.9-1.7-6.7-1.1-9 1.4-15 16.2-26.5 35.8-33.2 57.4-1 3.3.4 6.8 3.3 8.5l25.8 14.9c-2.6 14.1-2.6 28.5 0 42.6l-25.8 14.9c-3 1.7-4.3 5.2-3.3 8.5 6.7 21.6 18.2 41.1 33.2 57.4 2.3 2.5 6 3.1 9 1.4l25.8-14.9c10.9 9.3 23.4 16.5 36.9 21.3v29.8c0 3.4 2.4 6.4 5.7 7.1 22.3 5 45 4.8 66.2 0 3.3-.7 5.7-3.7 5.7-7.1v-29.8c13.5-4.8 26-12 36.9-21.3l25.8 14.9c2.9 1.7 6.7 1.1 9-1.4 15-16.2 26.5-35.8 33.2-57.4 1-3.3-.4-6.8-3.3-8.5l-25.8-14.9zM496 368.5c-26.8 0-48.5-21.8-48.5-48.5s21.8-48.5 48.5-48.5 48.5 21.8 48.5 48.5-21.7 48.5-48.5 48.5zM96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm224 32c1.9 0 3.7-.5 5.6-.6 8.3-21.7 20.5-42.1 36.3-59.2 7.4-8 17.9-12.6 28.9-12.6 6.9 0 13.7 1.8 19.6 5.3l7.9 4.6c.8-.5 1.6-.9 2.4-1.4 7-14.6 11.2-30.8 11.2-48 0-61.9-50.1-112-112-112S208 82.1 208 144c0 61.9 50.1 112 112 112zm105.2 194.5c-2.3-1.2-4.6-2.6-6.8-3.9-8.2 4.8-15.3 9.8-27.5 9.8-10.9 0-21.4-4.6-28.9-12.6-18.3-19.8-32.3-43.9-40.2-69.6-10.7-34.5 24.9-49.7 25.8-50.3-.1-2.6-.1-5.2 0-7.8l-7.9-4.6c-3.8-2.2-7-5-9.8-8.1-3.3.2-6.5.6-9.8.6-24.6 0-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h255.4c-3.7-6-6.2-12.8-6.2-20.3v-9.2zM173.1 274.6C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
Create a new User
</h3>
<div class="mt-2 mb-6">
<p class="text-sm text-gray-500">
Please provide the required information to add a new user.
</p>
</div>
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6">
<label
for="firstname"
class="block text-sm font-medium text-gray-700">{$_('first-name')}</label>
<input
use:focus
autocomplete="off"
placeholder={$_('first-name')}
class:border-red-500={!isFirstnameValid}
class:focus:border-red-500={!isFirstnameValid}
class:focus:ring-red-500={!isFirstnameValid}
bind:value={firstname_input_value}
bind:this={firstname_input}
type="text"
name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isFirstnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('first-name-is-required')}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="trackname"
class="block text-sm font-medium text-gray-700">{$_('middle-name')}</label>
<input
autocomplete="off"
placeholder={$_('middle-name')}
bind:value={middlename_input_value}
bind:this={middlename_input}
type="text"
name="trackname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
</div>
<div class="col-span-6">
<label
for="lastname"
class="block text-sm font-medium text-gray-700">Last Name</label>
<input
autocomplete="off"
placeholder="Last Name"
class:border-red-500={!isLastnameValid}
class:focus:border-red-500={!isLastnameValid}
class:focus:ring-red-500={!isLastnameValid}
bind:value={lastname_input_value}
bind:this={lastname_input}
type="text"
name="lastname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isLastnameValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('last-name-is-required')}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="password"
class="block text-sm font-medium text-gray-700">{$_('password')}</label>
<input
autocomplete="off"
placeholder={$_('password')}
class:border-red-500={!isPasswordValid}
class:focus:border-red-500={!isPasswordValid}
class:focus:ring-red-500={!isPasswordValid}
bind:value={password_input_value}
bind:this={password_input}
type="password"
name="password"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isPasswordValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
{$_('password-is-required')}
</span>
{/if}
</div>
<div class="col-span-6">
<label
for="trackname"
class="block text-sm font-medium text-gray-700">{$_('username')}</label>
<input
autocomplete="off"
placeholder={$_('username')}
bind:value={username_input_value}
bind:this={username_input}
type="text"
name="trackname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
</div>
<div class="col-span-6">
<label
for="email"
class="block text-sm font-medium text-gray-700">{$_('e-mail-adress')}</label>
<input
autocomplete="off"
placeholder={$_('e-mail-adress')}
bind:value={email_input_value}
bind:this={email_input}
type="email"
name="email"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 rounded-md p-2" />
{#if !isEmailValid}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">{$_('valid-email-is-required')}</span>
{/if}
</div>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
disabled={!createbtnenabled}
class:opacity-50={!createbtnenabled}
on:click={submit}
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
{$_('create')}
</button>
<button
on:click={() => {
modal_open = false;
}}
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
{$_('cancel')}
</button>
</div>
</div>
</div>
</div>
{/if}

View File

@ -0,0 +1,327 @@
<script>
import { _ } from "svelte-i18n";
import lodashIsEqual from "lodash.isequal";
import store from "../../store";
import isEmail from "validator/es/lib/isEmail";
import { UserService, UserGroupService } from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import PromiseError from "../base/PromiseError.svelte";
export let params;
const user_promise = UserService.userControllerGetOne(params.userid);
let data_loaded = false;
let usergroups_array_original = [];
const colors = [
"#f3558e",
"#17b978",
"#3498db",
"#3f3b3b",
"#775ada",
"#7ed6df_#000000",
"#000000",
"#21e6c1_#000000",
"#c0392b",
"#d35400",
"#7f8c8d",
"#6ab04c",
"#4834d4",
"#ff1f5a",
"#eac100",
];
let matched_colors = [];
$: delete_triggered = false;
$: original_data = {};
$: editable_userdata = {};
$: allgroups = [];
$: allgroups_ids = [];
$: usergroups_array = [];
$: search_permission = "";
user_promise.then((data) => {
let current_target = "";
let colorindex = -1;
// alphabetically sort permissions for color compatibility for target
data.permissions = data.permissions.sort();
data.permissions.forEach((p) => {
const target = p.split(":")[0];
if (current_target !== p.split(":")[0]) {
colorindex++;
current_target = p.split(":")[0];
}
let background = colors[colorindex];
let foreground = "#fff";
if (background.includes("_")) {
foreground = background.split("_")[1];
background = background.split("_")[0];
}
matched_colors[target] = [background, foreground];
});
//
data_loaded = true;
original_data = Object.assign(original_data, data);
editable_userdata = data;
data.groups.forEach((g) => {
usergroups_array = usergroups_array.concat([g.id]);
});
usergroups_array_original = usergroups_array;
allgroups.forEach((g) => {
allgroups_ids.push(g.id);
});
});
UserGroupService.userGroupControllerGetAll().then((data) => {
allgroups = data;
});
$: changes_performed = !lodashIsEqual(original_data, editable_userdata);
$: groups_changed =
JSON.stringify(usergroups_array) ===
JSON.stringify(usergroups_array_original);
$: save_enabled =
(changes_performed || !groups_changed) && isEmail(editable_userdata.email);
function submit() {
if (data_loaded === true && save_enabled) {
editable_userdata.groups = usergroups_array;
Toastify({
text: $_("updating-user"),
duration: 2500,
}).showToast();
UserService.userControllerPut(original_data.id, editable_userdata)
.then((resp) => {
Object.assign(original_data, resp);
Object.assign(editable_userdata, resp);
original_data.permissions = resp.permissions;
usergroups_array = [];
resp.groups.forEach((g) => {
usergroups_array = usergroups_array.concat([g.id]);
});
usergroups_array_original = usergroups_array;
//
Toastify({
text: $_("user-updated"),
duration: 2500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
})
.catch((err) => {});
}
}
function deleteUser() {
UserService.userControllerRemove(original_data.id, true)
.then((resp) => {
location.replace("./");
})
.catch((err) => {});
}
</script>
{#await user_promise}
<!-- -->
{:then user}
<section class="container p-5 select-none">
<div class="flex flex-row mb-4">
<div class="w-full">
<nav class="w-full flex">
<ol class="list-none flex flex-row items-center justify-start">
<li class="flex items-center">
<svg
class="flex-shrink-0 w-5 h-5 mr-2"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"><path
fill="currentColor"
d="M610.5 341.3c2.6-14.1 2.6-28.5 0-42.6l25.8-14.9c3-1.7 4.3-5.2 3.3-8.5-6.7-21.6-18.2-41.2-33.2-57.4-2.3-2.5-6-3.1-9-1.4l-25.8 14.9c-10.9-9.3-23.4-16.5-36.9-21.3v-29.8c0-3.4-2.4-6.4-5.7-7.1-22.3-5-45-4.8-66.2 0-3.3.7-5.7 3.7-5.7 7.1v29.8c-13.5 4.8-26 12-36.9 21.3l-25.8-14.9c-2.9-1.7-6.7-1.1-9 1.4-15 16.2-26.5 35.8-33.2 57.4-1 3.3.4 6.8 3.3 8.5l25.8 14.9c-2.6 14.1-2.6 28.5 0 42.6l-25.8 14.9c-3 1.7-4.3 5.2-3.3 8.5 6.7 21.6 18.2 41.1 33.2 57.4 2.3 2.5 6 3.1 9 1.4l25.8-14.9c10.9 9.3 23.4 16.5 36.9 21.3v29.8c0 3.4 2.4 6.4 5.7 7.1 22.3 5 45 4.8 66.2 0 3.3-.7 5.7-3.7 5.7-7.1v-29.8c13.5-4.8 26-12 36.9-21.3l25.8 14.9c2.9 1.7 6.7 1.1 9-1.4 15-16.2 26.5-35.8 33.2-57.4 1-3.3-.4-6.8-3.3-8.5l-25.8-14.9zM496 368.5c-26.8 0-48.5-21.8-48.5-48.5s21.8-48.5 48.5-48.5 48.5 21.8 48.5 48.5-21.7 48.5-48.5 48.5zM96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm224 32c1.9 0 3.7-.5 5.6-.6 8.3-21.7 20.5-42.1 36.3-59.2 7.4-8 17.9-12.6 28.9-12.6 6.9 0 13.7 1.8 19.6 5.3l7.9 4.6c.8-.5 1.6-.9 2.4-1.4 7-14.6 11.2-30.8 11.2-48 0-61.9-50.1-112-112-112S208 82.1 208 144c0 61.9 50.1 112 112 112zm105.2 194.5c-2.3-1.2-4.6-2.6-6.8-3.9-8.2 4.8-15.3 9.8-27.5 9.8-10.9 0-21.4-4.6-28.9-12.6-18.3-19.8-32.3-43.9-40.2-69.6-10.7-34.5 24.9-49.7 25.8-50.3-.1-2.6-.1-5.2 0-7.8l-7.9-4.6c-3.8-2.2-7-5-9.8-8.1-3.3.2-6.5.6-9.8.6-24.6 0-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h255.4c-3.7-6-6.2-12.8-6.2-20.3v-9.2zM173.1 274.6C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg>
</li>
<li class="flex items-center">
<a class="mr-2" href="./">{$_('users')}</a><svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="h-3 w-3 mr-2 stroke-current"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"><line
x1="5"
y1="12"
x2="19"
y2="12" />
<polyline points="12 5 19 12 12 19" /></svg>
</li>
<li class="flex items-center">
<span class="mr-2">{original_data.firstname}
{original_data.middlename || ''}
{original_data.lastname}</span>
</li>
</ol>
</nav>
</div>
</div>
<div class="mb-8 text-3xl font-extrabold">
{original_data.firstname}
{original_data.middlename || ''}
{original_data.lastname}
<span data-id="user_actions_${editable_userdata.id}">
{#if store.state.jwtinfo.userdetails.permissions.includes('USER:DELETE')}
{#if delete_triggered}
<button
on:click={deleteUser}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('confirm-delete')}</button>
<button
on:click={() => {
delete_triggered = !delete_triggered;
}}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-400 text-base font-medium text-white sm:w-auto sm:text-sm">{$_('cancel')}</button>
{/if}
{#if !delete_triggered}
<button
on:click={() => {
delete_triggered = true;
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('delete-user')}</button>
{/if}
{/if}
{#if !delete_triggered}
<button
disabled={!save_enabled}
class:opacity-50={!save_enabled}
type="button"
on:click={submit}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('save-changes')}</button>
{/if}
</span>
</div>
<div class="mt-3 text-sm w-full">
<p class="ml-1 font-medium text-gray-700">Profile Picture</p>
<img
alt={$_('profile-picture')}
class="h-20 w-20 rounded-full overflow-hidden bg-gray-100"
src={editable_userdata.profilePic} />
</div>
<div class="mt-3 text-sm w-full">
<label
for="enabled"
class="ml-1 font-medium text-gray-700">Active?</label>
<br />
<p class="text-gray-500">
<input
id="enabled"
on:change={() => {
editable_userdata.enabled = !editable_userdata.enabled;
}}
name="enabled"
type="checkbox"
checked={editable_userdata.enabled}
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" />
set the user active/ inactive
</p>
</div>
<div class="text-sm w-full">
<label
for="firstname"
class="font-medium text-gray-700">{$_('first-name')}</label>
<input
autocomplete="off"
placeholder={$_('first-name')}
type="text"
bind:value={editable_userdata.firstname}
name="firstname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 dark:bg-gray-900 dark:text-gray-100 rounded-md p-2" />
</div>
<div class="text-sm w-full">
<label
for="middlename"
class="font-medium text-gray-700">{$_('middle-name')}</label>
<input
autocomplete="off"
placeholder={$_('middle-name')}
type="text"
bind:value={editable_userdata.middlename}
name="middlename"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 dark:bg-gray-900 dark:text-gray-100 rounded-md p-2" />
</div>
<div class="text-sm w-full">
<label
for="lastname"
class="font-medium text-gray-700">{$_('last-name')}</label>
<input
autocomplete="off"
placeholder={$_('last-name')}
type="text"
bind:value={editable_userdata.lastname}
name="lastname"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 dark:bg-gray-900 dark:text-gray-100 rounded-md p-2" />
</div>
<div class="text-sm w-full">
<label
for="email"
class="font-medium text-gray-700">{$_('e-mail-adress')}</label>
<input
autocomplete="off"
placeholder={$_('e-mail-adress')}
type="email"
bind:value={editable_userdata.email}
name="email"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 dark:bg-gray-900 dark:text-gray-100 rounded-md p-2" />
</div>
{#if !isEmail(editable_userdata.email)}
<span
class="flex items-center font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">{$_('valid-email-is-required')}</span>
{/if}
<div class="text-sm w-full">
<label
for="username"
class="font-medium text-gray-700">{$_('username')}</label>
<input
autocomplete="off"
placeholder={$_('username')}
type="text"
bind:value={editable_userdata.username}
name="username"
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 dark:bg-gray-900 dark:text-gray-100 rounded-md p-2" />
</div>
<div class="text-sm w-full">
<span class="font-medium">{$_('groups')}</span>
<!-- svelte-ignore a11y-no-onchange -->
<select
bind:value={usergroups_array}
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 dark:bg-gray-900 dark:text-gray-100 rounded-md p-2"
multiple>
{#each allgroups as g}
{#if usergroups_array.includes(g.id)}
<option selected value={g.id}>{g.name}</option>
{:else}
<option value={g.id}>{g.name}</option>
{/if}
{/each}
</select>
</div>
<div class="text-sm w-full mt-8">
<p class="font-medium mb-4">
{$_('permissions')}
<a
class="px-4 py-2 bg-gray-500 rounded-md text-white"
href="/users/{params.userid}/permissions/">{$_('edit-permissions')}</a>
</p>
<div class="w-full sm:my-px sm:px-px sm:w-1/2">
<input
autocomplete="off"
placeholder="Search for permission"
type="text"
bind:value={search_permission}
class="mt-4 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm rounded-l-md sm:text-sm border-gray-300 border bg-gray-50 text-gray-500 dark:bg-gray-900 dark:text-gray-100 rounded-md p-2" />
</div>
{#each original_data.permissions as p}
{#if p.toLowerCase().includes(search_permission.toLowerCase())}
<span
style="background:{matched_colors[p.split(':')[0]][0]};color:{matched_colors[p.split(':')[0]][1]};"
class="mt-1 inline-flex items-center justify-center px-2 py-1 text-xs font-bold leading-none text-indigo-100 rounded">{p}</span>
<!-- -->
{/if}
{/each}
</div>
</section>
{:catch error}
<PromiseError {error} />
{/await}

View File

@ -0,0 +1,241 @@
<script>
import { _ } from "svelte-i18n";
import {
UserService,
PermissionService,
CreatePermission,
} from "@odit/lfk-client-js";
import Toastify from "toastify-js";
import PromiseError from "../base/PromiseError.svelte";
export let params;
let [
grantedPermissions_initial,
grantedPermissions,
inheritedPermissions,
to_add,
to_delete,
allpermissions,
promises,
] = [[], [], [], [], [], [], []];
$: original_data = {};
$: save_enabled =
JSON.stringify(grantedPermissions) ===
JSON.stringify(grantedPermissions_initial);
const user_promise = UserService.userControllerGetOne(params.userid);
user_promise.then((data) => {
original_data = Object.assign(original_data, data);
});
function submit() {
Toastify({
text: "updating permissions...",
duration: 2500,
}).showToast();
to_delete.forEach((d) => {
promises = promises.concat([
PermissionService.permissionControllerRemove(d, true),
]);
});
to_add.forEach((a) => {
promises = promises.concat([
PermissionService.permissionControllerPost(a),
]);
});
Promise.all(promises).then((values) => {
promises = [];
to_delete.forEach((d) => {
to_delete = to_delete.filter((o) => o !== d);
});
to_add.forEach((a) => {
to_add = to_add.filter(
(o) => o.target + ":" + o.action !== a.target + ":" + a.action
);
});
Toastify({
text: "Permissions updated!",
duration: 2500,
backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
}).showToast();
});
}
Object.values(CreatePermission.target).forEach((t) => {
Object.values(CreatePermission.action).forEach((a) => {
allpermissions = allpermissions.concat([{ target: t, action: a }]);
});
});
UserService.userControllerGetPermissions(params.userid).then((val) => {
val.inherited.forEach((p) => {
inheritedPermissions = inheritedPermissions.concat([p]);
});
val.directlyGranted.forEach((p) => {
grantedPermissions = grantedPermissions.concat([p]);
});
grantedPermissions_initial = grantedPermissions;
});
</script>
{#await user_promise}
<!-- -->
{:then user}
<section class="container p-5 select-none">
<div class="flex flex-row mb-4">
<div class="w-full">
<nav class="w-full flex">
<ol class="list-none flex flex-row items-center justify-start">
<li class="flex items-center">
<svg
class="flex-shrink-0 w-5 h-5 mr-2"
fill="currentColor"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"><path
fill="currentColor"
d="M610.5 341.3c2.6-14.1 2.6-28.5 0-42.6l25.8-14.9c3-1.7 4.3-5.2 3.3-8.5-6.7-21.6-18.2-41.2-33.2-57.4-2.3-2.5-6-3.1-9-1.4l-25.8 14.9c-10.9-9.3-23.4-16.5-36.9-21.3v-29.8c0-3.4-2.4-6.4-5.7-7.1-22.3-5-45-4.8-66.2 0-3.3.7-5.7 3.7-5.7 7.1v29.8c-13.5 4.8-26 12-36.9 21.3l-25.8-14.9c-2.9-1.7-6.7-1.1-9 1.4-15 16.2-26.5 35.8-33.2 57.4-1 3.3.4 6.8 3.3 8.5l25.8 14.9c-2.6 14.1-2.6 28.5 0 42.6l-25.8 14.9c-3 1.7-4.3 5.2-3.3 8.5 6.7 21.6 18.2 41.1 33.2 57.4 2.3 2.5 6 3.1 9 1.4l25.8-14.9c10.9 9.3 23.4 16.5 36.9 21.3v29.8c0 3.4 2.4 6.4 5.7 7.1 22.3 5 45 4.8 66.2 0 3.3-.7 5.7-3.7 5.7-7.1v-29.8c13.5-4.8 26-12 36.9-21.3l25.8 14.9c2.9 1.7 6.7 1.1 9-1.4 15-16.2 26.5-35.8 33.2-57.4 1-3.3-.4-6.8-3.3-8.5l-25.8-14.9zM496 368.5c-26.8 0-48.5-21.8-48.5-48.5s21.8-48.5 48.5-48.5 48.5 21.8 48.5 48.5-21.7 48.5-48.5 48.5zM96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm224 32c1.9 0 3.7-.5 5.6-.6 8.3-21.7 20.5-42.1 36.3-59.2 7.4-8 17.9-12.6 28.9-12.6 6.9 0 13.7 1.8 19.6 5.3l7.9 4.6c.8-.5 1.6-.9 2.4-1.4 7-14.6 11.2-30.8 11.2-48 0-61.9-50.1-112-112-112S208 82.1 208 144c0 61.9 50.1 112 112 112zm105.2 194.5c-2.3-1.2-4.6-2.6-6.8-3.9-8.2 4.8-15.3 9.8-27.5 9.8-10.9 0-21.4-4.6-28.9-12.6-18.3-19.8-32.3-43.9-40.2-69.6-10.7-34.5 24.9-49.7 25.8-50.3-.1-2.6-.1-5.2 0-7.8l-7.9-4.6c-3.8-2.2-7-5-9.8-8.1-3.3.2-6.5.6-9.8.6-24.6 0-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h255.4c-3.7-6-6.2-12.8-6.2-20.3v-9.2zM173.1 274.6C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z" /></svg>
</li>
<li class="flex items-center">
<a class="mr-2" href="./">{$_('users')}</a><svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="h-3 w-3 mr-2 stroke-current"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"><line
x1="5"
y1="12"
x2="19"
y2="12" />
<polyline points="12 5 19 12 12 19" /></svg>
</li>
<li class="flex items-center">
<span class="mr-2"><a href="../">{original_data.firstname}
{original_data.middlename || ''}
{original_data.lastname}</a></span>
</li>
<li class="flex items-center">
<svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="h-3 w-3 mr-2 stroke-current"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"><line
x1="5"
y1="12"
x2="19"
y2="12" />
<polyline points="12 5 19 12 12 19" /></svg>
</li>
<li class="flex items-center">
<span class="mr-2">Permissions</span>
</li>
</ol>
</nav>
</div>
</div>
<div class="mb-8 text-3xl font-extrabold">
Permissions:
{original_data.firstname}
{original_data.middlename || ''}
{original_data.lastname}
<span>
{#if promises.length === 0}
<button
disabled={save_enabled}
class:opacity-50={save_enabled}
type="button"
on:click={submit}
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">{$_('save-changes')}</button>
{:else}
<button
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-yellow-600 text-base font-medium text-white hover:bg-yellow-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-yellow-500 sm:ml-3 sm:w-auto sm:text-sm">Applying
Changes</button>
{/if}
</span>
</div>
<!-- -->
<div class="flex flex-wrap -mx-1 overflow-hidden">
<div class="my-1 px-1 w-full overflow-hidden sm:w-1/3">verfügbare</div>
<div class="my-1 px-1 w-full overflow-hidden sm:w-1/3">erteilte</div>
<div class="my-1 px-1 w-full overflow-hidden sm:w-1/3">geerbte</div>
</div>
<!-- -->
<div class="flex flex-wrap -mx-1 overflow-hidden">
{#if allpermissions.length > 0}
<div class="my-1 px-1 w-full overflow-hidden sm:w-1/3">
<div
class="border-4 border-dashed rounded mb-4 p-5 text-lg text-center">
{#each allpermissions as p}
{#if !grantedPermissions.includes(p)}
<p
class="block w-full mt-1 text-sm dark:border-gray-600 dark:bg-gray-700 bg-gray-200 p-2 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:text-gray-300 dark:focus:shadow-outline-gray form-input">
{p.target + ':' + p.action}
<button
on:click={() => {
grantedPermissions = grantedPermissions.concat([p]);
if (to_delete.some((o) => o === p.id)) {
to_delete = to_delete.filter((o) => o !== p.id);
} else {
to_add = to_add.concat([
{
action: p.action,
target: p.target,
principal: original_data.id,
},
]);
}
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-green-200 text-base font-medium text-black hover:bg-green-700 hover:text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 sm:ml-3 sm:w-auto sm:text-sm">+</button>
</p>
{/if}
{/each}
</div>
</div>
<div class="my-1 px-1 w-full overflow-hidden sm:w-1/3">
<div
class="border-4 border-dashed rounded mb-4 p-5 text-lg text-center">
{#each grantedPermissions as p}
<p
class="block w-full mt-1 text-sm dark:border-gray-600 dark:bg-gray-700 bg-gray-200 p-2 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:text-gray-300 dark:focus:shadow-outline-gray form-input">
{p.target + ':' + p.action}
<button
on:click={() => {
grantedPermissions = grantedPermissions.filter((o) => o.target + ':' + o.action !== p.target + ':' + p.action);
if (to_add.some((o) => o.target + ':' + o.action === p.target + ':' + p.action)) {
to_add = to_add.filter((o) => o.target + ':' + o.action !== p.target + ':' + p.action);
} else {
to_delete = to_delete.concat([p.id]);
}
}}
type="button"
class="w-full justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-300 text-base font-medium text-black hover:bg-red-700 hover:text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:ml-3 sm:w-auto sm:text-sm">-</button>
</p>
{/each}
</div>
</div>
<div class="my-1 px-1 w-full overflow-hidden sm:w-1/3">
<div
class="border-4 border-dashed rounded mb-4 p-5 text-lg text-center">
{#each inheritedPermissions as p}
<p
class="block w-full mt-1 text-sm dark:border-gray-600 dark:bg-gray-700 bg-gray-200 p-2 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:text-gray-300 dark:focus:shadow-outline-gray form-input">
{p.target + ':' + p.action}
</p>
{/each}
</div>
</div>
{/if}
</div>
</section>
{:catch error}
<PromiseError {error} />
{/await}

Some files were not shown because too many files have changed in this diff Show More