Compare commits

..
Author SHA1 Message Date
Dominik K.andGitHub 0ba2893610 fix: hardcode blog url 2025-07-22 00:43:27 +02:00
MazeandGitHub 6399f1c902 Merge pull request #405 from mezotv/main
fix: typo in url
2025-07-22 00:39:20 +02:00
Dominik Koch e5c8df5555 fix: typo in url 2025-07-22 00:34:49 +02:00
MazeandGitHub 11b270ffd3 Merge pull request #404 from mezotv/fix/blog
fix: move to next_public keys
2025-07-22 00:30:54 +02:00
Dominik K.andGitHub 1b0dd48f3d Merge branch 'main' into fix/blog 2025-07-22 00:29:46 +02:00
Dominik Koch b10c8536fa fix: move to next_public keys 2025-07-22 00:27:23 +02:00
MazeandGitHub bd05f64511 Merge pull request #403 from mezotv/fix/readme
fix: marble env keys in readme
2025-07-22 00:10:34 +02:00
Dominik Koch fe2762904c fix: marble env keys 2025-07-22 00:07:04 +02:00
izaandGitHub 8c1ea7e680 Merge pull request #402 from mezotv/feat/opencut-blog
feat: add blog powered by marble
2025-07-22 00:59:57 +03:00
Dominik Koch ddf0353d92 fix: use default marble data 2025-07-21 23:50:21 +02:00
Dominik Koch 5ef6e2bc97 fix: implement coderabbit suggestions
They actually good wtf
2025-07-21 23:45:02 +02:00
Dominik Koch 7ea4b31626 fix: remove unused siteName 2025-07-21 23:31:00 +02:00
Dominik Koch e21c467489 feat: add opencut blog 2025-07-21 23:18:07 +02:00
Maze Winther 3446fdde87 Merge branch 'staging' 2025-07-21 18:44:27 +02:00
Maze Winther b360e8d985 Merge pull request #336 2025-07-21 18:42:47 +02:00
Maze Winther b87a62372d Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-07-21 18:17:02 +02:00
Maze Winther fb51cc2597 Merge branch 'staging' 2025-07-21 18:16:54 +02:00
Maze Winther ad771a0b56 Merge pull request [#336](https://github.com/mazeincoding/AppCut/issues/336) from mannix-lei/main 2025-07-21 18:00:48 +02:00
Maze Winther 5782df7350 remove toasts 2025-07-21 14:47:42 +02:00
Maze Winther a2a9bef1d8 fix: undo for resizing timeline elements 2025-07-21 14:46:28 +02:00
izaandGitHub 1b3d42b2a9 Merge pull request #389 from mezotv/fix/early-alpha
fix: switch to alpha
2025-07-21 15:31:18 +03:00
Dominik Koch 935ccea85e fix: switch to alpha 2025-07-21 14:27:27 +02:00
izaandGitHub 11e2e1a49d Merge pull request #379 from vagxrth/feature/better-github-icon
[REFACTOR]: replace GitHub icon with FontAwesome version in footer
2025-07-21 14:53:56 +03:00
Maze Winther aeb9cfc6b8 fix: AZERTY support for keyboard shortcuts 2025-07-21 12:23:09 +02:00
Maze Winther 4c2d8c52ab feat: basic audio view in media panel 2025-07-21 11:52:48 +02:00
Maze Winther e74e60d5bf style: height in media panel 2025-07-21 11:52:35 +02:00
Vagarth 15c57239f4 refactor: replace GitHub icon with FontAwesome version in footer 2025-07-21 07:01:31 +05:30
Maze Winther 263f14ef2a Merge branch 'staging' 2025-07-20 22:30:30 +02:00
Maze Winther 4896dce268 hero: cleanup and fix button to redirect 2025-07-20 21:48:47 +02:00
Maze Winther 07e068b658 feat: get hero ready for beta 2025-07-20 21:46:50 +02:00
Maze Winther efabec8bcb feat: make button on header consistent for beta 2025-07-20 21:44:20 +02:00
Maze Winther c7c394b2ef feat: onboarding 2025-07-20 21:42:02 +02:00
Maze Winther cac4fcaab8 styling: dialog responsiveness 2025-07-20 21:38:41 +02:00
Maze Winther ab12258432 feat: remove protection from middleware 2025-07-20 21:02:06 +02:00
Maze Winther ba3da43728 Merge branch 'staging' 2025-07-20 16:36:21 +02:00
Maze Winther baaf773b80 Merge branch 'staging' of https://github.com/mazeincoding/AppCut into staging 2025-07-20 16:13:49 +02:00
Maze Winther d7c9a2391c fix: import 2025-07-20 16:12:38 +02:00
izaandGitHub afa8f426fb Merge pull request #264 from Chubercik/optimal_pngs
Optimise PNGs across the repo
2025-07-20 11:31:43 +03:00
izaandGitHub 3c4d539a08 Merge pull request #278 from phamtanminhtien/chore/ignore-ds-store
chore: add .DS_Store to .gitignore
2025-07-20 11:27:34 +03:00
Hyteq be1ae20d90 format 2025-07-20 11:00:22 +03:00
Hyteq ede08dac41 Merge branch 'main' into staging 2025-07-20 10:50:28 +03:00
Hyteq fe020be6ce Revert "format, cleanup, deps"
This reverts commit eab8ec3ba8.
2025-07-20 10:50:17 +03:00
Hyteq eab8ec3ba8 format, cleanup, deps 2025-07-20 10:48:40 +03:00
Maze Winther 33d8caf65c Merge pull request #357 2025-07-19 21:03:13 +02:00
Maze Winther 24e13f692a feat: hello world 2025-07-19 20:51:14 +02:00
Maze Winther ac962d023c Merge pull request #279 2025-07-19 19:18:01 +02:00
Maze Winther 5266aed5eb feat: ripple editing mode on timeline 2025-07-19 19:08:08 +02:00
Maze Winther 42ae8a7530 fix: ensure dragging/resizing an element on the timeline doesn't deselect it 2025-07-19 16:19:06 +02:00
Maze Winther 0d95f032cc fix: wrong logic on snapping button 2025-07-19 16:14:48 +02:00
Maze Winther a9b7043829 refactor: improve timeline elements, fix few bugs 2025-07-18 18:05:15 +02:00
Maze Winther 6ce8434615 fix: element trimming, allow infinite trimStart for text/image elements 2025-07-18 12:52:11 +02:00
Maze Winther d98638bfe6 disable databuddy in development 2025-07-18 12:51:44 +02:00
Anwarul Islam 0e1311bfe0 feat: ripple editing support sync with latest timeline changes 2025-07-18 16:43:50 +06:00
Anwarul Islam 0b12fdfa0a feat: implement ripple-aware track removal and element deletion in timeline store 2025-07-18 16:43:50 +06:00
Anwarul Islam d5bc63357b feat: implement overlap checking and resolution for timeline elements during ripple editing 2025-07-18 16:43:50 +06:00
Anwarul Islam d440280f97 feat: add overlap checking and resolution functions for timeline elements 2025-07-18 16:43:50 +06:00
Anwarul Islam 63e07dc9ff feat: replace setRippleEditingEnabled with toggleRippleEditing method 2025-07-18 16:43:50 +06:00
Anwarul Islam e3e26a207f feat: enhance element deletion with ripple editing support 2025-07-18 16:43:50 +06:00
Anwarul Islam ffd82087c7 feat: integrate ripple editing into element start time updates 2025-07-18 16:43:50 +06:00
Anwarul Islam 53fe61f684 feat: implement ripple editing functionality in timeline component 2025-07-18 16:43:50 +06:00
Anwarul Islam 8b1c4de01b feat: add ripple editing functionality to timeline store 2025-07-18 16:43:50 +06:00
Maze Winther 85b6123510 fix: z-index of playhead 2025-07-18 11:54:32 +02:00
Maze Winther f8ccb673bf style: match editing experience to match capcut 2025-07-18 11:51:47 +02:00
Maze Winther 74e7d427aa Merge branch 'feat/shortcut-actions' into staging 2025-07-18 11:37:48 +02:00
MazeandGitHub d040ca53cb Merge pull request #342 from anwarulislam/feat/kbd
chore: keyboard shortcuts and improve key display
2025-07-18 11:29:36 +02:00
Maze Winther c4e6a9a45a Merge branch 'feat/kbd' into staging 2025-07-18 11:27:02 +02:00
Maze Winther b19db0bbea cleanup 2025-07-18 11:26:49 +02:00
Maze Winther dc5416da07 refactor: remove unused functions 2025-07-18 11:20:17 +02:00
Anwarul Islam 23001f8442 feat: define keybinding types and refactor keybinding configuration 2025-07-18 15:19:46 +06:00
Maze Winther f9bfc30959 Merge branch 'fix/new-project-has-old-media' into staging 2025-07-18 11:16:30 +02:00
Anwarul Islam c3caefc001 feat: enhance keybinding import validation and improve error handling 2025-07-18 08:30:42 +06:00
Anwarul Islam 8e4661951d feat: enhance keybinding management by integrating keybindingsEnabled state and updating related hooks 2025-07-18 08:05:02 +06:00
Anwarul Islam cdae10ca0a feat: refactor keybinding management and add keybinding editor component 2025-07-18 07:38:32 +06:00
Anwarul Islam 33531fb3bf feat: implement keybindings listener and disabler composables in use-keybindings hook 2025-07-18 07:37:12 +06:00
Anwarul Islam 4137747aba feat: add keybindings store with default configurations and utility functions 2025-07-18 07:35:31 +06:00
Kha Nguyen c2ef774525 remove unused state 2025-07-17 20:29:13 -05:00
Kha Nguyen e4748ecb09 Revert "fix: prevent multiple project creation when project doesn't exist"
This reverts commit d04aa8d1c1.
2025-07-17 20:25:16 -05:00
Anwarul Islam d328e009d4 Refactor keyboard shortcuts management: introduce useKeyboardShortcutsHelp hook and update KeyBadge and ShortcutItem components 2025-07-18 07:09:05 +06:00
Anwarul Islam f3bffae7ea Refactor editor provider to integrate keybinding management and action handlers 2025-07-18 07:08:57 +06:00
Anwarul Islam 7eda7975fc Implement editor actions for playback and timeline editing 2025-07-18 07:08:46 +06:00
Kha Nguyen d04aa8d1c1 fix: prevent multiple project creation when project doesn't exist 2025-07-17 20:08:08 -05:00
Anwarul Islam 1afc51eb9d Enhance action types and arguments for playback controls 2025-07-18 07:07:52 +06:00
Anwarul Islam 01b11226a3 Add additional keybindings for navigation and editing actions 2025-07-18 07:07:47 +06:00
Anwarul Islam 4b80c14982 Normalize keyboard shortcut keys to lowercase for consistent handling 2025-07-18 06:27:31 +06:00
Anwarul Islam c391eb8616 Refactor keyboard shortcuts to use uppercase keys for display and update KeyBadge functionality 2025-07-18 06:22:49 +06:00
Kha Nguyen e7bad87217 fix: remove wrong check causing new project to show old medias 2025-07-17 19:17:21 -05:00
Anwarul Islam f5d221feec Refactor KeyBadge component to return key names directly and update ShortcutItem to use <kbd> for key display 2025-07-18 05:53:42 +06:00
Anwarul Islam c34b402297 Add utils for platform detection 2025-07-18 05:53:19 +06:00
Anwarul Islam 480e83fa79 Add shortcut key styles to globals.css 2025-07-18 05:52:31 +06:00
Anwarul Islam 7f609cb86f Add action and keybinding management with React hooks 2025-07-18 05:27:40 +06:00
Maze Winther f492ac9aad fix: timeline element resizing 2025-07-17 23:51:12 +02:00
Maze Winther 499f06cb52 style: timeline tracks content 2025-07-17 23:02:29 +02:00
yassinehaimouch e487ca254e fix: vercel link to push you to home page not dashboard 2025-07-17 19:20:29 +01:00
Maze Winther f1b216848d Merge branch 'staging' 2025-07-17 19:51:31 +02:00
Maze Winther b564f7c239 middleware access 2025-07-17 19:50:32 +02:00
Maze Winther 648f0b8529 stable timeline 2025-07-17 19:46:23 +02:00
Maze Winther 0b919d4df8 refactor all timeline components 2025-07-17 19:33:10 +02:00
Maze Winther 56e6edcc5a move timeline component 2025-07-17 17:37:18 +02:00
Maze Winther 0217303532 formatting 2025-07-17 17:34:48 +02:00
Maze Winther 4170ee4f5b cleanup: tailwind classes 2025-07-17 17:34:18 +02:00
Maze Winther 4e5e9756c8 Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-07-17 17:22:53 +02:00
MazeandGitHub b97a30824e Merge pull request #283 from KhacTuanIT/feature/optimize-timeline-ruler
Optimize timeline performance
2025-07-17 17:22:42 +02:00
Maze Winther e9ec13999a Merge branch 'main' into staging 2025-07-17 17:19:30 +02:00
Maze Winther ce47e88e34 Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-07-17 17:19:23 +02:00
MazeandGitHub 502725b98e Merge pull request #289 from anwarulislam/feat/readme-clarfication
Update README and contributing guidelines for clarity
2025-07-17 17:18:47 +02:00
MazeandGitHub 103e4d5d3f Merge pull request #311 from enkeii64/feat/search-sort-projects
feat: add search and sort functionality to projects page
2025-07-17 17:17:20 +02:00
Maze Winther bb9befc626 cleanup 2025-07-17 17:14:29 +02:00
Maze Winther fe5b208a18 Merge branch 'feat/search-sort-projects' into staging 2025-07-17 17:11:56 +02:00
Maze Winther 22bf42d397 Merge branch 'staging' of https://github.com/mazeincoding/AppCut into staging 2025-07-17 17:08:36 +02:00
Maze Winther fef9d9a3fb ??? typescript 2025-07-17 17:08:33 +02:00
Anwarul Islam f3307a64d1 docs: reorganize prerequisites and setup instructions for clarity 2025-07-17 20:12:34 +06:00
Anwarul Islam 4d028487e6 docs: clarify Docker setup note in contributing guidelines 2025-07-17 20:02:46 +06:00
Anwarul Islam f1fed51502 docs: update setup instructions to include environment file copying steps 2025-07-17 19:52:42 +06:00
Anwarul Islam 6691e74db9 docs: update contributing guidelines for clarity on setup instructions 2025-07-17 19:52:42 +06:00
Anwarul Islam f21b704323 docs: update README for clarity on setup instructions 2025-07-17 19:46:57 +06:00
Khắc TuấnandGitHub faf5a121ac Merge branch 'main' into feature/optimize-timeline-ruler 2025-07-17 20:32:08 +07:00
Maze Winther f94b1a86d6 fuck yan 2025-07-17 14:17:47 +02:00
Maze Winther b18a48b586 refactor: move sorting logic to project store 2025-07-17 13:45:12 +02:00
MazeandGitHub d768da9216 Merge pull request #319 from anwarulislam/fix/tabbar
fix: make tab bar container full width
2025-07-17 13:40:32 +02:00
MazeandGitHub 021c3fb7d0 Merge pull request #331 from simonorzel26/patch-1
Update pull_request_template.md
2025-07-17 13:39:10 +02:00
Maze Winther e577c9659f Merge branch 'staging' of https://github.com/mazeincoding/AppCut into staging 2025-07-17 13:38:16 +02:00
MazeandGitHub b1efc90157 Merge pull request #321 from vadi25/main
Project creation and project access denied outside of dev mode
2025-07-17 13:35:44 +02:00
Simon OandGitHub 4dd1ea635b Update pull_request_template.md 2025-07-17 13:27:59 +02:00
MazeandGitHub 5a7a089f0e Merge pull request #323 from khanguyen74/feature/regenerate-media-items-thumbnail
fix: regenerate video thumbnails when a project is loaded
2025-07-17 13:06:06 +02:00
MazeandGitHub 9bf5ce399c Merge pull request #324 from simonorzel26/feat/refactor-timeline
feat(web): timeline refactor and horizontal + vertical scrolling
2025-07-17 13:02:30 +02:00
enkeii64 8efc696dcd feat: update delete project dialog 2025-07-17 20:54:31 +10:00
vadi25andGitHub 9af8905c0c Merge branch 'OpenCut-app:main' into main 2025-07-17 12:20:47 +02:00
Maze Winther 7d4cbca119 docs: README 2025-07-17 11:51:20 +02:00
Maze Winther 51857f1709 docs: what to focus on section 2025-07-17 11:50:28 +02:00
Simon Orzel 167f366c6d feat: bunx biome format --write . 2025-07-17 03:00:38 +02:00
Simon Orzel 1505c1b12f feat: timeline changes 2025-07-17 02:53:25 +02:00
Simon Orzel 98b2103286 feat: working 2025-07-17 02:25:24 +02:00
Simon Orzel 9fa9f406fe feat: splits working code 2025-07-17 01:59:39 +02:00
Simon Orzel 0d2894feda feat: working z indexing and borders 2025-07-17 01:13:38 +02:00
Kha Nguyen 36df8f2afe chore: format 2025-07-16 16:55:35 -05:00
Kha Nguyen 9f3ba9905d fix: regenerate video thumbnails when loading project 2025-07-16 16:49:08 -05:00
Simon Orzel 6ff4bcd093 feat: starts to refactor timeline 2025-07-16 23:31:51 +02:00
Maze Winther a3c84e4a19 fix handlebars 2025-07-16 23:28:26 +02:00
Maze Winther 36516b874f remove onboarding 2025-07-16 22:50:40 +02:00
Maze Winther 387404661a Merge branch 'vadi25/main' into staging 2025-07-16 22:49:55 +02:00
Maze Winther 7d1f99c20c fix 2025-07-16 22:49:10 +02:00
Maze Winther 651cfbfccb Merge branch 'main' of https://github.com/vadi25/opencut into vadi25/main 2025-07-16 22:47:23 +02:00
Maze Winther accad442f4 cleanup 2025-07-16 22:47:17 +02:00
MazeandGitHub 39ac2cbeb6 Merge pull request #322 from Zaid-maker/type-fix
MERGE
2025-07-16 22:44:12 +02:00
Maze Winther 38035fe5d0 Merge branch 'sahilsobhani/main' into staging 2025-07-16 22:42:36 +02:00
Maze Winther 4851c38e74 style(footer): align everything to left 2025-07-16 22:39:33 +02:00
Zaid-maker 2b618b189c more 2025-07-17 01:10:49 +05:00
Zaid-maker 10f5691d9e fix typo 2025-07-17 00:28:35 +05:00
Maze Winther 87dd275e57 fix: no more rounded corners in preview 2025-07-16 21:05:28 +02:00
vadi25 0988a936ad added redirect to project page if users not authorized, to be change by rolegate in the future 2025-07-16 20:37:13 +02:00
vadi25 6e3a9ec7ec fixed so only Projects can be created in production, so DB is safe from being saturated in production 2025-07-16 20:24:16 +02:00
Sahil Sobhani 04a1036770 added .env.example 2025-07-16 23:35:25 +05:30
Sahil Sobhani caf6fef3ad Redesigned the footer for mobile devices: UI Changes 2025-07-16 23:34:20 +05:30
MazeandGitHub da973b2e1e Merge pull request #318 from Zaid-maker/fix-builds
Add BETTER_AUTH_SECRET and BETTER_AUTH_URL env vars to build job
2025-07-16 19:50:53 +02:00
Anwarul Islam 4e7b088647 fix: make tab bar container full width 2025-07-16 23:35:11 +06:00
Maze Winther 5f58662ec7 Merge branch 'main' into staging 2025-07-16 19:13:04 +02:00
Zaid-maker e419eba29b fix build by passing correct env 2025-07-16 21:34:03 +05:00
Maze Winther 218625b61a feat: advance snapping 2025-07-16 17:44:44 +02:00
Maze Winther c1c4b114eb fix: snapping for both directions (left/right) 2025-07-16 17:38:29 +02:00
Maze Winther fcfe612cd6 fix: handlers component on mobile 2025-07-16 17:36:47 +02:00
Maze Winther e7344d9120 Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-07-16 16:15:11 +02:00
Maze Winther c3de532709 fix 2025-07-16 16:14:45 +02:00
enkeii64 9d5707d5fd fix: resolve merge conflict in draggable-item.tsx 2025-07-17 00:12:41 +10:00
enkeii64 7cbbbe1630 fix: resolve merge conflict in draggable-item.tsx 2025-07-17 00:07:39 +10:00
izaandGitHub 3b93fd6b68 Merge pull request #315 from OpenCut-app/staging
Staging
2025-07-16 17:06:36 +03:00
enkeii64 e0a5d4860a fix(ui): add tooltipText prop to PlusButton 2025-07-17 00:06:22 +10:00
Maze Winther 20f73bf705 Merge branch 'staging' of https://github.com/mazeincoding/AppCut into staging 2025-07-16 16:05:35 +02:00
Maze Winther 0c086e3c24 more shortcut fixes 2025-07-16 16:05:30 +02:00
izaandGitHub f6e2d5a60f Merge pull request #314 from mezotv/feat/sponsored-by-vercel
feat(hero): add sponsored by vercel to hero
2025-07-16 16:59:36 +03:00
gantiro 955f6331f2 Merge branch 'main' into feature/optimize-timeline-ruler 2025-07-16 20:53:00 +07:00
Maze Winther 5483db83de fix keyboard shortcuts 2025-07-16 15:52:25 +02:00
Maze Winther 3371a68896 block access in prod 2025-07-16 15:48:30 +02:00
Maze Winther fbd14290c6 another fix 2025-07-16 15:45:45 +02:00
Maze Winther 1389ce469a Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-07-16 15:44:09 +02:00
Maze Winther e317fb547d revert and fix (please work) 2025-07-16 15:44:05 +02:00
Maze Winther 8432f517cc please 2025-07-16 15:42:30 +02:00
Maze Winther 94f1c720bb fix 2025-07-16 15:41:03 +02:00
Maze Winther 453c422f94 fix 2025-07-16 15:36:16 +02:00
Maze Winther 700366f61e fix issue 2025-07-16 15:34:55 +02:00
Maze Winther 9530058b97 add waitlist count back 2025-07-16 15:30:19 +02:00
Dominik Koch 18d2186101 feat(hero): add sponsored by vercel to hero 2025-07-16 15:19:06 +02:00
Khắc TuấnandGitHub 2974fd89a9 Merge branch 'main' into feature/optimize-timeline-ruler 2025-07-16 20:04:27 +07:00
Maze Winther 1e01364740 Merge branch 'main' into staging 2025-07-16 14:53:16 +02:00
Maze Winther 0554a031c2 man idk 2025-07-16 14:52:53 +02:00
enkeii64 f83f647299 refactor: improve type safety and security in projects page 2025-07-16 22:43:01 +10:00
enkeii64 fe1c0edb8f feat: add no results screen for project search 2025-07-16 22:37:19 +10:00
Maze Winther 8bc26539eb cleanup (this doesn't work) 2025-07-16 14:37:08 +02:00
enkeii64 5a76196221 feat: add search and sort functionality to projects page 2025-07-16 22:23:21 +10:00
Maze Winther 527d588897 chore: bump lucide react verion 2025-07-16 13:40:34 +02:00
Maze Winther 49d467cd83 Merge branch 'staging' into pr/vishesh711/284 2025-07-16 13:38:26 +02:00
Maze Winther e57272043f Merge branch 'staging' 2025-07-16 13:30:19 +02:00
Maze Winther 9ae7cf0691 Merge branch 'main' into staging 2025-07-16 13:29:54 +02:00
MazeandGitHub 025b0100d3 Merge pull request #288 from simonorzel26/feat/select-all-projects-button-clickable
feat: makes the select all project button entirely clickable and cleans up small css changes
2025-07-16 13:29:36 +02:00
Maze Winther 6281652239 fix docs 2025-07-16 13:15:41 +02:00
Maze Winther 5b10e84542 Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-07-16 13:11:43 +02:00
Maze Winther 96f7516c94 Merge branch 'staging' of https://github.com/mazeincoding/AppCut into staging 2025-07-16 12:22:15 +02:00
Maze Winther a7e23ec714 media drag overlay cleanup 2025-07-16 12:22:09 +02:00
Maze Winther 63d5b2f110 Merge branch 'feat/media-button' into staging 2025-07-16 12:04:09 +02:00
Maze Winther 446745a33c cleanup 2025-07-16 12:03:44 +02:00
MazeandGitHub e96d97885b Merge pull request #307 from W-DOS0/fix-header
Update README.md
2025-07-16 12:01:28 +02:00
MazeandGitHub ccca2e8d0f Merge pull request #308 from enkeii64/feat/display-all-contributors
feat: display all contributors
2025-07-16 12:00:11 +02:00
MazeandGitHub 2982293b54 Merge pull request #287 from simonorzel26/fix/268_multiple-new-projects
fix(web): #268 multiple new projects bug
2025-07-16 11:58:56 +02:00
enkei64andGitHub 0cb26ef088 Merge branch 'OpenCut-app:main' into feat/display-all-contributors 2025-07-16 19:07:33 +10:00
[W]DOS_ 2aa1eac7da Update README.md
Removed the duplicate “## Getting Started” header to improve clarity and avoid redundancy in the setup instructions.
2025-07-16 10:54:59 +02:00
MazeandGitHub 27731a270d Merge pull request #291 from ahmetskilinc/better-rate-limiting
feat: better-rate-limiting
2025-07-16 10:54:38 +02:00
enkeii64 7c6b2e4f49 feat: display all contributors 2025-07-16 18:49:11 +10:00
Ahmet Kilinc 79f6c680f3 remove browser check 2025-07-16 01:23:06 +01:00
Ahmet Kilinc ece9c78b16 coderabbit comments 2025-07-16 01:05:42 +01:00
Ahmet Kilinc eb49500592 adds Vercel BotId 2025-07-16 01:00:40 +01:00
Ahmet Kilinc 5178732c4c csrf token and browser detection 2025-07-16 00:44:42 +01:00
Simon Orzel f76169ada1 feat: refactors media drag overlay and media tab 2025-07-16 00:48:00 +02:00
Simon Orzel acd1e2e216 feat: makes the select all project button entirely clickable and cleans up small css changes 2025-07-15 23:48:47 +02:00
Maze Winther d5c52557df Merge branch 'staging' 2025-07-15 23:28:06 +02:00
Maze Winther 83d65bdd15 feat: remove waitlist count due to bots 2025-07-15 23:26:04 +02:00
Simon Orzel 6df492b661 fix(web): #268 multiple new projects bug 2025-07-15 23:25:09 +02:00
vishesh711 13db27c173 fix: resolve TypeScript build errors and deployment blockers
- Fix missing lucide-react icon imports in type declarations
  - Add 40+ missing icon exports (Loader2, ExternalLink, Type, etc.)
  - Fix ArrowLeft → ChevronLeft imports in auth pages
  - Resolve LucideIcon type declaration issue

- Fix invalid Button variant 'ghost' → 'text' in KeyboardShortcutsHelp

- Ensure project builds successfully with Bun package manager
- Address CodeRabbit review feedback for production readiness

These fixes resolve all TypeScript compilation errors and make the
project fully deployment-ready. Build now passes with zero errors.
2025-07-15 12:58:04 -04:00
vishesh711 d3204f6cd7 feat: Add comprehensive keyboard shortcuts system for video editor
- Implement industry-standard video editing shortcuts (Space, J/K/L, arrows)
- Add playback controls: Space (play/pause), J/K/L (rewind/pause/forward)
- Add navigation: arrow keys (frame stepping), Shift+arrows (5s jumps), Home/End
- Add editing shortcuts: S (split at playhead), N (toggle snapping)
- Add selection shortcuts: Ctrl/Cmd+A (select all), Ctrl/Cmd+D (duplicate)
- Add keyboard shortcuts help dialog with categorized shortcuts display
- Integrate help button in editor header for discoverability
- Add TypeScript type declarations for better development experience
- Fix Docker build environment variable validation issues

All shortcuts follow professional video editing standards (Avid/Premiere/Final Cut).
Context-aware shortcuts that don't interfere with text input fields.
Toast notifications provide user feedback for better UX.
2025-07-15 12:36:53 -04:00
gantiro faf317cd20 fix comment remove check time 2025-07-15 23:33:31 +07:00
gantiro 7cc3a907de fix comment for use-timeline-playhead hook 2025-07-15 23:29:30 +07:00
gantiro 8bfc91e62e fix comment 2025-07-15 23:26:56 +07:00
gantiro 5fc06257f2 change zIndex for timeline-playhead 2025-07-15 23:23:15 +07:00
gantiro 52f003f25a add comment for hooks 2025-07-15 22:56:56 +07:00
gantiro b266678fe4 Optimize timeline performance 2025-07-15 22:51:38 +07:00
Maze Winther 3a8d57921e feat: add lock icon toggle for snapping functionality 2025-07-15 17:41:10 +02:00
Maze Winther fd2aa00074 style: tooltip 2025-07-15 17:40:19 +02:00
Maze Winther 7a725ecd09 cleanup 2025-07-15 17:29:28 +02:00
Maze Winther a32ffb601a Merge branch 'feat/thumbnail-appearance' into staging 2025-07-15 17:07:21 +02:00
Maze Winther dc0e4783fb cleanup 2025-07-15 16:55:04 +02:00
Maze Winther fdeddbe3ed Merge branch 'feature/plus-button-timeline' into staging 2025-07-15 16:28:08 +02:00
Pham Tan Minh Tien 7732f0f158 chore: add .DS_Store to .gitignore 2025-07-15 20:37:06 +07:00
Maze Winther 1f95bb98d6 Merge branch 'staging' 2025-07-15 15:08:00 +02:00
Maze Winther b1ccf3695e fix quicl 2025-07-15 15:07:47 +02:00
Maze Winther a8a5a68cae Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-07-15 15:03:56 +02:00
Maze Winther 7d2f323f4d fix: last updated date in roadmap 2025-07-15 15:03:33 +02:00
Maze Winther d16f2cc3c1 i love turbopack 2025-07-15 15:00:04 +02:00
Maze Winther ebad05f17b refactor: remove development debug 2025-07-15 14:58:08 +02:00
Maze Winther ce851e52ce Merge branch 'staging' of https://github.com/mazeincoding/AppCut into staging 2025-07-15 14:57:09 +02:00
Maze Winther 10b6cc98b8 chore: bump next version in bun lock 2025-07-15 14:57:05 +02:00
Maze Winther 2432bcf54b chore: bump next version 2025-07-15 14:56:48 +02:00
MazeandGitHub f5ed3e196a Merge pull request #266 from anwarulislam/feat/snapping
feature: add timeline snapping functionality
2025-07-15 14:17:50 +02:00
Maze Winther 1fdf2a0515 cleanup 2025-07-15 14:16:54 +02:00
Maze Winther 5e86ed9efc fix: tooltip styling 2025-07-15 14:13:02 +02:00
Maze Winther cb877769fb refactor: remove google auth 2025-07-15 12:35:08 +02:00
MazeandGitHub 0e83b6c15e Merge pull request #272 from vietnamesekid/main
feat: integrate @t3-oss/env for environment variable management across the application
2025-07-15 12:20:55 +02:00
MazeandGitHub 2aa7493189 Merge pull request #274 from Zaid-maker/migration
Set default value of users.email_verified column to false via migration
2025-07-15 12:17:44 +02:00
enkeii64 d7c764f968 feat: Add Discord link to footer 2025-07-15 19:24:34 +10:00
Zaid-maker bfa5d8f062 migration 2025-07-15 13:51:17 +05:00
tranminhquang 9d5fb821c7 feat: integrate @t3-oss/env for environment variable management across the application 2025-07-15 13:29:16 +07:00
Anwarul Islam 0f05d25e8a fix: ensure correct type assertion for snappedTime in useTimelineSnapping 2025-07-15 07:17:45 +06:00
Anwarul Islam e3cb491a29 feat: add snap point notification to TimelineTrackContent for improved snapping feedback 2025-07-15 07:06:29 +06:00
Anwarul Islam 2cc25b70d5 feat: enhance snapping functionality by adding snap point handling in Timeline component 2025-07-15 07:03:36 +06:00
Anwarul Islam 9cc77b4499 feat: enforce non-negative snap threshold and minimum grid interval in timeline store 2025-07-15 06:59:46 +06:00
Anwarul Islam 59f4098f75 refactor: remove keyboard shortcuts for better integration later 2025-07-15 04:32:36 +06:00
Anwarul Islam 1106aeeb60 feat: integrate snapping functionality into timeline track component 2025-07-15 04:27:19 +06:00
Anwarul Islam af632d8cc5 feat: add snapping functionality and controls to timeline component 2025-07-15 04:27:12 +06:00
Anwarul Islam e7485340cf feat: implement useTimelineSnapping hook for snapping functionality in timeline 2025-07-15 04:25:51 +06:00
Anwarul Islam 2b57f3026c feat: add snapping settings and actions to timeline store 2025-07-15 04:25:16 +06:00
Anwarul Islam 4de81796a3 feat: add SnapIndicator component for timeline snapping visualization 2025-07-15 04:23:45 +06:00
Maze Winther 55978c30fa docs: vercel sponsor to readme 2025-07-14 23:53:33 +02:00
Jakub Marcowski 7fb1181034 Optimise PNGs across the repo 2025-07-14 23:16:38 +02:00
Anwarul Islam d53321ec36 fix: enhance media tile display with aspect ratio adjustments and overlay borders 2025-07-15 00:42:19 +06:00
Maze Winther 7798334300 feat: roadmap page and move all open-graph images to /open-graph folder 2025-07-14 20:34:16 +02:00
Anwarul Islam 2c941b1585 fix: optimize media thumbnail display by removing tiling utility and using CSS background instead 2025-07-15 00:12:01 +06:00
Anwarul Islam c628b5a8ee fix improve timeline element visual display by tiling repeating media thumbnails 2025-07-14 23:53:11 +06:00
Anwarul Islam 1e64bc4abd fix improve timeline element visual display 2025-07-14 23:49:48 +06:00
Maze Winther 0309f9cf08 refactor: comment 2025-07-14 19:48:12 +02:00
ni-zavandGitHub ffb254c339 Merge branch 'OpenCut-app:main' into refactor/move-add-button 2025-07-14 23:43:34 +07:00
Maze Winther 416511084a fix: drag media to timeline 2025-07-14 18:06:11 +02:00
Nizav acd42a29f6 refactor: move the add button below the placeholder text 2025-07-14 22:46:28 +07:00
Maze Winther 64b84ca041 fix: toast 2025-07-14 17:42:00 +02:00
Maze Winther 87215b31e9 fix: last updated date in privacy/terms 2025-07-14 17:39:13 +02:00
Maze Winther d8bb13eb78 feat: privacy/terms pages 2025-07-14 17:37:25 +02:00
Maze Winther b54c5443a0 fix: metadata 2025-07-14 16:21:41 +02:00
Maze Winther 89691312b8 chore: bump drizzle-orm version 2025-07-14 16:20:52 +02:00
Maze Winther 73ba7d6577 fix: better auth env variable 2025-07-14 16:16:10 +02:00
Maze Winther 238f8e85bb chore: add missing env variables to turbo.json 2025-07-14 13:07:20 +02:00
Maze Winther b0e9901730 databuddy: enable errors 2025-07-13 13:25:43 +02:00
Maze Winther 3b4b7e2009 databuddy 2025-07-13 13:20:06 +02:00
Maze Winther 3037a7ecbf fix: handle border 2025-07-12 21:52:35 +02:00
Maze Winther ad7ace3fd2 fix: spacing 2025-07-12 21:52:18 +02:00
ahmedfahim21 03b3c952c7 feat: click to add to timeline 2025-07-13 00:59:54 +05:30
Anwarul Islam d0769fdcaa fix: reorder export statements for consistency 2025-07-12 23:53:15 +06:00
Anwarul Islam 9756559811 chore: remove GripVertical as it is an unused import 2025-07-12 23:52:17 +06:00
Anwarul Islam 0ee043b319 fix: enhance layout of editor and resizable components for better usability 2025-07-12 23:49:49 +06:00
Anwarul Islam 9d828063c1 fix: update ResizableHandle styles for improved visibility and interaction 2025-07-12 23:40:54 +06:00
Anwarul Islam 70517fec18 fix: improve layout and styling of resizable panels 2025-07-12 23:40:38 +06:00
Maze Winther 22db0b8b89 Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-07-12 17:15:06 +02:00
Maze Winther 71f666c6d7 feat: optimzation for deleting projects 2025-07-12 17:14:53 +02:00
Maze Winther e57ca15dc6 fix: missing name field 2025-07-12 16:54:55 +02:00
MazeandGitHub e96832bf9b Merge pull request #172 from Zaid-maker/bump-bun-version
Bump bun version
2025-07-12 16:52:42 +02:00
Maze Winther 0a0f68d711 Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-07-12 16:51:18 +02:00
Maze Winther 57dace960c Merge branch 'manuarora700/main' 2025-07-12 16:51:09 +02:00
Maze Winther 145d01c8e4 cleanup 2025-07-12 16:50:34 +02:00
MazeandGitHub 25d12a6fa4 Merge pull request #183 from youknowom/improve-contributing
docs: add note about npm workspace error and bun recommendation
2025-07-12 16:39:29 +02:00
MazeandGitHub a9b02df2e3 Merge pull request #185 from omraval18/fix/login-redirect-issue
fix: redirects to /projects after login instead of /editor
2025-07-12 16:38:32 +02:00
Maze Winther b76e84354d feat: dark mode always 2025-07-12 15:45:12 +02:00
Maze Winther 233f78bf52 Merge remote-tracking branch 'anwarulislam/feat/fix-scroll' 2025-07-12 15:44:33 +02:00
MazeandGitHub f0b0451616 Merge pull request #200 from ahmedfahim21/feature/gesture-navigation-fix
fix: prevent unintended navigation on swipe
2025-07-12 15:39:14 +02:00
MazeandGitHub 4d0382cc4b Merge pull request #202 from MarcBlattmann/patch-1
Remove Unused Bracket in readme
2025-07-12 15:38:13 +02:00
MazeandGitHub 800e720e6e Merge pull request #208 from Sanjit-K/main
fixed: [BUG] crypto.randomUUID is not a function Runtime Error #205. …
2025-07-12 15:27:00 +02:00
MazeandGitHub 457d828c45 Merge pull request #210 from Mirza-Samad-Ahmed-Baig/main
refactor: improve theme handling, env var safety, and db schema
2025-07-12 15:24:01 +02:00
MazeandGitHub d925b62a47 Merge pull request #215 from Chirag-varu/main
Better README and docker-compose port error solved
2025-07-12 15:22:51 +02:00
Maze Winther 84d153e2c5 databuddy 2025-07-12 14:42:31 +02:00
Chirag-varu 27da1838cc Better README and docker-compose port error solved 2025-07-12 17:05:53 +05:30
Maze Winther 8545d95070 refactor: add FPS and remove turbopack (yes, fuck you turbopack) 2025-07-12 12:48:31 +02:00
Maze Winther 0726c27221 small change 2025-07-12 11:52:54 +02:00
Maze Winther 2de4c7c153 feat: remove playback speed from timeline 2025-07-12 11:45:43 +02:00
Maze Winther ba809effb6 databuddy 2025-07-12 11:29:35 +02:00
Maze Winther 09577b712a Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-07-12 11:24:05 +02:00
Maze Winther da06c5946d databuddy 2025-07-12 11:23:54 +02:00
Mirza-Samad-Ahmed-Baig d48a3fe287 refactor: improve theme handling, env var safety, and db schema 2025-07-12 13:31:50 +05:00
ahmedfahim21 b4c93182d0 fix: apply suggestion 2025-07-12 12:45:50 +05:30
Sanjit K 799fd2981a fixed: [BUG] crypto.randomUUID is not a function Runtime Error #205. Added fallback function with manual UUID generation if crypto.randomUUID() was not available. 2025-07-12 00:14:03 -05:00
MarcandGitHub ac1eef7bd0 Update README.md 2025-07-11 22:18:32 +02:00
MarcandGitHub 6e6f5211fc Remove Unused Bracket in readme 2025-07-11 22:05:35 +02:00
ahmedfahim21 338e13a601 fix: apply suggestion 2025-07-12 01:04:29 +05:30
ahmedfahim21 410f8da1c9 fix: prevent unintended navigation on swipe 2025-07-12 00:39:17 +05:30
Anwarul Islam 6ecc359d80 chore: clean up 2025-07-11 20:02:09 +06:00
MazeandGitHub fd017d6aca docs: add growth to README 2025-07-11 15:59:56 +02:00
Anwarul Islam 4d8760d0e1 refactor: enhance scroll synchronization for timeline and track labels 2025-07-11 19:58:35 +06:00
Maze Winther 9b78503562 fix styling 2025-07-11 14:50:51 +02:00
Maze Winther 76229a1da5 colors 2025-07-11 04:21:40 +02:00
Maze Winther ac0d089bf7 fix: dragging time ruler would trigger the selection box 2025-07-11 04:19:39 +02:00
Maze Winther 4d67e366ad feat: background settings (color, blur) 2025-07-11 03:52:39 +02:00
Maze Winther 6c19dbb6bb feat: selection box 2025-07-11 01:37:42 +02:00
Maze Winther d643a9a277 small refactor 2025-07-11 00:57:09 +02:00
Maze Winther 92d534760d fix: ensure element doesn't go above playhead 2025-07-11 00:50:33 +02:00
Maze Winther 4880e3b10c fix: allow real-time playhead on ruler and cursor change 2025-07-11 00:48:17 +02:00
Maze Winther 3a241d9112 feat: improve playhead 2025-07-11 00:25:07 +02:00
Maze Winther bb65d4fb96 refactor: timeline playhead component and hook 2025-07-10 23:55:08 +02:00
Maze Winther eabcdb0988 fix: preview image scaling 2025-07-10 23:45:59 +02:00
Maze Winther c86d200297 fix: increase max text size to 300 2025-07-10 23:22:57 +02:00
Maze Winther 3d6786a587 fix: make text scale based on preview size 2025-07-10 23:22:40 +02:00
Maze Winther f43021e994 background 2025-07-10 22:40:01 +02:00
Maze Winther 4fc14947ad fix: tailwind class 2025-07-10 22:21:09 +02:00
Maze Winther 6c59fed5c0 style: font picker 2025-07-10 21:53:50 +02:00
Maze Winther 7ec9167aeb refactor: new font picker component 2025-07-10 21:52:46 +02:00
Maze Winther f984f615ce style: text area bg 2025-07-10 21:49:27 +02:00
Maze Winther fe6492f359 refactor: separate property item component 2025-07-10 21:48:26 +02:00
Maze Winther ed4e9dad19 style: padding 2025-07-10 21:39:11 +02:00
Maze Winther eadd6940e4 feat: font size to text panel 2025-07-10 21:37:44 +02:00
Maze Winther 0acead5bb1 refactor: move properties to dedicated folder 2025-07-10 21:24:47 +02:00
Maze Winther 98d536a474 formatting 2025-07-10 21:23:50 +02:00
Maze Winther 9bbb42c357 dropdown fix 2025-07-10 21:15:43 +02:00
Maze Winther 445d01fc8f font size 2025-07-10 21:14:33 +02:00
Maze Winther 3ada352730 tailwind 2025-07-10 21:13:46 +02:00
Maze Winther 6aa071ef8d fix: tailwind 2025-07-10 21:08:31 +02:00
omraval18 3e45be5c47 fix: redirects to /projects after login instead of /editor 2025-07-11 00:33:31 +05:30
Maze Winther e8b0057cc4 feat: initial fonts support 2025-07-10 20:53:58 +02:00
Maze Winther 055a6af055 refactor: move constants to src/constants 2025-07-10 20:35:15 +02:00
Maze Winther 1376bee16d fix: improve element dragging (can drag outside timeline without it breaking) 2025-07-10 20:10:01 +02:00
Maze Winther 0f175b232f textarea size 2025-07-10 19:58:57 +02:00
Maze Winther aa0482b012 fix: select auto focus when closed 2025-07-10 19:58:39 +02:00
Maze Winther 0223c34a1e fix: timestamp different widths 2025-07-10 19:58:03 +02:00
Maze Winther 44f504f401 style: timestamp 2025-07-10 19:56:54 +02:00
Omkaar d9d54df431 docs: update note to mention npm upgrade option 2025-07-10 23:20:32 +05:30
Maze Winther 4d0c3268cc style: properties panel text area 2025-07-10 19:44:24 +02:00
Omkaar 5e74906e19 docs: add note about npm workspace error and bun recommendation 2025-07-10 23:12:57 +05:30
Maze Winther 2fe67febd6 complete 1459cd 2025-07-10 14:30:12 +02:00
Maze Winther 3a34485cc7 style: text box in properties panel 2025-07-10 14:28:08 +02:00
Maze Winther 1459cd7232 feat: audio for playback 2025-07-10 14:23:13 +02:00
Maze Winther f8e8de4438 Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-07-09 23:59:49 +02:00
Maze Winther 8dd6f9a9b3 feat: add conditional rendering to properties panel with text change functionality 2025-07-09 23:59:39 +02:00
Maze Winther 7bf6671c0a feat: add conditional rendering to properties panel with text size functionality 2025-07-09 23:59:13 +02:00
Maze Winther b6aa8e10d6 fix: improve keyboard event handling in timeline component 2025-07-09 23:25:08 +02:00
Maze Winther 6ba1021149 fix: make panels resizable 2025-07-09 22:46:10 +02:00
Maze Winther 059a4f4205 style: timeline tracks 2025-07-09 22:31:45 +02:00
Maze Winther f4fbdf14a9 style: text align 2025-07-09 22:17:54 +02:00
Maze Winther db8cd93a99 style: replace track color circle with icon in timeline 2025-07-09 22:17:05 +02:00
Maze Winther 27d65ca7c5 fix: make timeline zoom faster 2025-07-09 22:07:39 +02:00
Maze Winther 53d6d0e1af fix: zoom on entire timeline 2025-07-09 22:07:25 +02:00
Maze Winther 3089fb0418 refactor: move timeline zoom to custom hook 2025-07-09 22:01:30 +02:00
Maze Winther 346368cf75 I FOUND THE C (removed it) 2025-07-09 21:54:52 +02:00
Maze Winther 5e1f780fff style: completely fresh ui in editor 2025-07-09 21:45:25 +02:00
Maze Winther 3d685f57dd fix: styling in preview 2025-07-09 21:07:42 +02:00
Maze Winther 612fc03cde refactor: fix import syntax in page.tsx 2025-07-09 21:04:41 +02:00
Maze Winther aadc253fee refactor: clean up imports in page.tsx 2025-07-09 21:04:24 +02:00
Maze Winther c02f276303 refactor: fresh properties panel 2025-07-09 21:03:56 +02:00
Maze Winther dd35c91f39 fix: update header link from Editor to Projects for better navigation 2025-07-09 20:56:19 +02:00
Maze Winther 51894544b2 fix: add padding to Why Not CapCut page for improved layout 2025-07-09 14:05:25 +02:00
Manu Arora c5d96a0ded add: actual handlebars to resize container 2025-07-09 17:30:37 +05:30
Maze Winther 612fa55937 fix: enhance tone and clarity in Why Not CapCut page content 2025-07-09 10:39:21 +02:00
Maze Winther 9c25814717 fix: update text in Why Not CapCut page for improved tone 2025-07-09 10:35:20 +02:00
Maze Winther 9c8985d115 redirect 2025-07-09 07:49:17 +02:00
Maze Winther 7a706f3bbc vercel 2025-07-09 07:43:35 +02:00
Maze Winther 44ff4fe638 chore: remove vercel config 2025-07-09 07:41:19 +02:00
Maze Winther e5892fdea6 vercel 2025-07-09 07:39:00 +02:00
Maze Winther ad45c8c1ed chore: vercel config 2025-07-09 07:36:05 +02:00
Maze Winther c6cfb8ce87 feat: why not capcut page 2025-07-09 07:33:10 +02:00
Maze Winther 813dbcb9c2 refactor: move timeline element context menu into timeline-element.tsx 2025-07-08 23:51:46 +02:00
Maze Winther 66da1e20d3 feat: remove cascade deletion logic when removing media 2025-07-08 23:38:27 +02:00
Maze Winther 3ef17cecb4 refactor: centralize logic to zustand store 2025-07-08 22:51:42 +02:00
Maze Winther 24b9c89084 refactor: remove unused import 2025-07-08 22:43:08 +02:00
Maze Winther c60098987f fix: media item not right-clickable 2025-07-08 22:42:55 +02:00
Maze Winther 60a1273206 fix: adjust element border colors 2025-07-08 20:54:19 +02:00
Maze Winther 91d89f56d7 fix: don't render audio on preview 2025-07-08 20:54:05 +02:00
Zaid HafeezandGitHub b5d04d591f Update bun-ci.yml 2025-07-08 13:29:03 +05:00
Zaid HafeezandGitHub e26c3bccdb Update package.json 2025-07-08 13:25:13 +05:00
Zaid HafeezandGitHub 74408541fd Update package.json 2025-07-08 13:24:33 +05:00
Zaid HafeezandGitHub 65afd3f18b bump bun version in CI 2025-07-08 13:23:41 +05:00
Maze Winther ea59cc3950 fix: ensure timeline clips can be extended to longer than 5s 2025-07-08 00:42:46 +02:00
Maze Winther c0cc4c009e fix: ensure preview toolbar is always at bottom 2025-07-08 00:10:31 +02:00
Maze Winther d750d7f41d shipping too hard 2025-07-08 00:07:22 +02:00
Maze Winther 9d2fd50fbc refactor: centralize track colors and timeline constants 2025-07-07 23:21:06 +02:00
Maze Winther d36df2fb62 refactor: remove element menu from timeline element component 2025-07-07 23:05:31 +02:00
Maze Winther acda7064bd fix: unable to resize elements to be bigger 2025-07-07 23:03:35 +02:00
Maze Winther f3763b8465 refactor: move element resize logic to react hook 2025-07-07 19:13:20 +02:00
Maze Winther bd0c7f2206 refactor: store media relative to project, add storage for timeline data, and other things 2025-07-07 19:06:36 +02:00
MazeandGitHub 11c0b89bd1 Merge pull request #158 from Pratiyankkumar/added-repo-url
feat: make Open Source badge a clickable link to GitHub repository
2025-07-07 03:52:51 +02:00
MazeandGitHub 25c9ffc131 Merge pull request #164 from Pratiyankkumar/select-multiple-projects
feat: add multiple project selection and bulk delete functionality
2025-07-07 03:46:48 +02:00
Maze Winther 85a93ce090 fix: primary/foreground button 2025-07-06 23:59:26 +02:00
Maze Winther 6edd5b36cf feat: main track and ordering tracks 2025-07-06 22:51:11 +02:00
Maze Winther 40c7fbb4f8 refactor: move to a typed-tracks system and add support for text 2025-07-06 20:45:29 +02:00
Maze Winther 0e32c732dd fix: ghost around draggable item (maybe) 2025-07-05 18:56:46 +02:00
Maze Winther 13b2fad50f docs: add sponsors section to README and include Vercel support information 2025-07-05 02:57:41 +02:00
Maze Winther 3d1efeaf36 Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-07-05 02:51:50 +02:00
MazeandGitHub 163489f499 Merge pull request #166 from ArjunCodess/patch-1
docs: Replace placeholder repo URL with actual GitHub clone link in README
2025-07-04 16:22:48 +02:00
Arjun Vijay PrakashandGitHub 562cf38341 Update README.md 2025-07-04 07:16:46 -07:00
Maze Winther d04ba1468e style: change border color for selected timeline clips from primary to foreground 2025-07-04 15:15:53 +02:00
Maze Winther 4728884931 refactor: new reusable draggable-item component and use it 2025-07-04 01:30:24 +02:00
Maze Winther fb9f47117c style: update primary color 2025-07-04 01:02:14 +02:00
Maze Winther 9dbfa980c2 fix: ensure selecting an already selected clip doesn't deselect it 2025-07-04 00:37:59 +02:00
Maze Winther 8be05901fb style: update card background color in dark theme 2025-07-03 22:29:16 +02:00
Maze Winther 37d684748f refactor: replace div with Card component 2025-07-03 22:29:00 +02:00
Maze Winther b5af50b0d8 style: add select-none class to Default text in TextView component 2025-07-03 22:26:27 +02:00
Maze Winther c413b53c33 Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-07-03 22:07:44 +02:00
Maze Winther 434a832d8e feat: add TextView component to media panel 2025-07-03 22:07:33 +02:00
Maze Winther 1372c218ad feat: add custom scrollbar utilities to Tailwind configuration 2025-07-03 22:07:24 +02:00
Maze Winther 8f365915a2 refactor: replace TextIcon with TypeIcon in media panel tabs 2025-07-03 22:06:43 +02:00
Maze Winther f991d707ea feat: enhance media panel tab navigation with scroll functionality 2025-07-03 22:06:24 +02:00
Maze Winther 59a6c539a1 feat: implement media panel with tab navigation 2025-07-03 20:43:23 +02:00
Maze Winther ef0828a13d style: color in preview panel 2025-07-03 17:26:49 +02:00
Maze Winther b90f9922a1 fix drag in media panel and add toolbar 2025-07-03 17:26:32 +02:00
pratiyankkumar d3d5bbf51a feat: add multiple project selection and bulk delete functionality 2025-07-02 19:39:08 +05:30
Hyteq b1ade266e5 fix: CDN instead of app for databooty 2025-07-02 16:25:08 +03:00
pratiyankkumar a4d7bdda24 feat: make Open Source badge a clickable link to GitHub repository 2025-07-02 09:50:28 +05:30
Maze Winther bc3fbec541 docs: update README 2025-07-02 01:39:32 +02:00
Billionaire DevandGitHub dcf3fccca1 Updated Readme 2025-07-01 21:17:26 +01:00
Maze Winther 5d02169d63 fix: deselect clips by clicking outside of track 2025-07-01 19:48:00 +02:00
Maze Winther d95b7a9316 fix right-clicking a clip allows dragging 2025-07-01 19:42:09 +02:00
Maze Winther 8bf865df0d feat: select clip when it's being right-clicked 2025-07-01 19:31:25 +02:00
Maze Winther b14e9e82fe docs: yeah 2025-07-01 19:26:42 +02:00
Maze Winther 394d9f684c refactor: clean up comments 2025-07-01 19:16:37 +02:00
Maze Winther 5dfe9c0aac fix: better drag ux on playhead 2025-07-01 17:47:07 +02:00
Maze Winther a3309b4c45 refactor: replace native select/input with shadcn 2025-07-01 17:07:44 +02:00
Maze Winther fe289db9b0 refactor: remove unused icon imports from media panel 2025-07-01 16:17:14 +02:00
Maze Winther 849fb3d2af refactor: editor header 2025-07-01 16:06:36 +02:00
Maze Winther 364e541d57 fix: remove success toasts for added media items in timeline components 2025-07-01 16:03:51 +02:00
Maze Winther d623ba6b4b refactor: simplify canvas preset names for clarity 2025-07-01 16:02:32 +02:00
Maze Winther fb487681b6 fix: select focus styling 2025-07-01 16:02:07 +02:00
Maze Winther a16c86092a fix: playhead not moving in real time 2025-07-01 15:30:50 +02:00
Maze Winther baf5e9907f fix: remove 4k aspect ratio 2025-07-01 15:06:10 +02:00
Maze Winther e4683e38db style: make select component look similar to other components 2025-07-01 15:05:45 +02:00
Maze Winther ac4ff63438 fix: ensure playback ruler appears above everything else 2025-07-01 15:02:34 +02:00
Maze Winther ee973cad21 feat: select clip when being dragged, even if it wasn't selected 2025-07-01 15:00:21 +02:00
Maze Winther 9c8594d8f3 style: timeclip's focus state 2025-07-01 14:56:14 +02:00
Maze Winther c37c64c1b9 feat: add time formatting utility and update editor and preview components to use it 2025-07-01 01:30:02 +02:00
Maze Winther 1a01871cfc refactor: update media processing to use width and height instead of aspect ratio 2025-07-01 01:13:14 +02:00
Maze Winther 9b37ce6610 style: give preview panel a fresh look 2025-07-01 00:58:21 +02:00
Maze Winther d11d835c7c style: fresh look on media panel 2025-07-01 00:13:41 +02:00
Maze Winther 1fa4c9c72f feat: update tools panel size from 25 to 45 2025-06-30 23:35:57 +02:00
Maze Winther 50e3d92b92 style: set aspect ratio to 16:9 for the asset containers in the media panel 2025-06-30 23:35:37 +02:00
Maze Winther 9e01efdc88 refactor: consolidate default panel sizes into a constant for improved maintainability 2025-06-30 23:30:36 +02:00
Maze Winther 011be3d9a5 refactor: remove unused imports 2025-06-30 23:25:25 +02:00
Maze Winther b474ad6b15 feat: hide preview canvas when nothing in timeline 2025-06-30 23:23:18 +02:00
Maze Winther 3e916f0f00 feat: implement automatic canvas size adjustment based on first media item aspect ratio 2025-06-30 23:19:37 +02:00
Maze Winther d50cd0b40d fix: update editor link in header component to include default editor ID 2025-06-30 21:44:46 +02:00
Maze Winther d0ae75d0b4 fix ts error 2025-06-30 20:01:18 +02:00
Maze Winther 09373eb4a3 feat: implement project management features with storage integration, including project creation, deletion, and renaming dialogs 2025-06-30 19:58:36 +02:00
Maze Winther cd30c205b4 fix: await addMediaItem in timeline component to ensure proper item processing 2025-06-30 19:57:13 +02:00
Maze Winther 16a319f2e4 delete editor.css 2025-06-30 18:32:47 +02:00
Maze Winther 1466dd42e2 feat: remove contributor index display from contributors page 2025-06-29 23:37:39 +02:00
Maze Winther b461234c65 refactor: separate timeline track content into new file 2025-06-29 23:20:17 +02:00
Maze Winther 822323d883 refactor: update timeline to use context menu component 2025-06-29 23:09:40 +02:00
Maze Winther ca29be23ff style: make context menu consistent to dropdown 2025-06-29 23:09:06 +02:00
Maze Winther 796308e68e style: make everything consistent in dropdown 2025-06-29 23:08:52 +02:00
Maze Winther 507d6a6a7e style: dropdown menu 2025-06-29 22:15:06 +02:00
Maze Winther c414b83bc4 style: remove shadow from card 2025-06-29 22:14:31 +02:00
Maze Winther e4f2ce9221 fix: auto-focus on dropdown trigger and hover effect disappearing 2025-06-29 22:04:35 +02:00
Maze Winther bfba482098 feat: add project page with mock data and update project type to include thumbnail 2025-06-29 21:32:51 +02:00
Maze Winther 3bc00f8e40 fix: don't hard-code colors in header 2025-06-29 20:09:43 +02:00
Maze Winther 02d7a92e06 fix: hero height 2025-06-29 19:38:30 +02:00
Maze 4e0352d4d6 Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-06-29 14:48:26 +02:00
Maze Winther 8aa0aeb6e3 style: hero, minor tweaks 2025-06-29 14:36:14 +02:00
pratiyankkumar 90eaa40bc6 fix: Context Menu Placement on edges 2025-06-28 21:39:03 +05:30
MazeandGitHub 306c2885f1 Merge pull request #141 from sinanptm/code-refactoring
fix(issue): Adress the issue(#109)
2025-06-28 12:44:01 +02:00
Maze Winther 5a6872537e feat: dedicated footer 2025-06-28 12:38:31 +02:00
Maze Winther 8fe09c83b1 fix: DATABASE_URL error on setup 2025-06-27 16:56:59 +02:00
muhammed sinan 901d0baafd added memoization 2025-06-27 10:58:21 +05:30
muhammed sinan 8934cc610f refactor: added auth hooks for handling sign and signup logics 2025-06-27 10:46:35 +05:30
izaandGitHub cdfb5ea7b0 Merge pull request #138 from karansingh21202/feat/timeline-scroll-sync-minimal-v2
fix: Updated synchronize timeline ruler and tracks scrolling for long videos
2025-06-27 07:04:12 +03:00
izaandGitHub dc99516a3f Merge pull request #125 from sinanptm/code-refactoring
Removed unused File apps/web/src/components/auth-form.tsx
2025-06-27 07:03:32 +03:00
izaandGitHub 1f257d30dc Merge pull request #137 from dipanshurdev/patch-01
added: twitter with good looking icons
2025-06-27 07:02:51 +03:00
izaandGitHub f53f2802eb Merge pull request #135 from DevloperAmanSingh/feature/player-controls
Feature: More Players Controls are added now .
2025-06-27 07:02:28 +03:00
DevloperAmanSingh c32daa4f2e refactor: enhance timeline component by improving drag-and-drop functionality and clip selection handling 2025-06-27 07:56:18 +05:30
Maze Winther 87e90a5e24 fix dragging clips in timeline 2025-06-27 03:50:39 +02:00
Maze Winther 089f7f8d71 Merge branch 'fix-issue-#131' 2025-06-27 01:28:23 +02:00
Maze Winther 01140fd7bb Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-06-27 01:02:03 +02:00
Maze Winther dfde7592bb refactor: separate timeline toolbar into new component and add new TrackType type 2025-06-27 01:02:01 +02:00
MazeandGitHub 6391147a96 Merge pull request #134 from dipanshurdev/minor-feat
fix(style): README.md header section
2025-06-27 00:57:17 +02:00
Maze Winther 679ebc02b5 Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-06-27 00:55:03 +02:00
Maze Winther d5db470150 style: update dark theme color variables 2025-06-27 00:55:00 +02:00
Karan Singh f3c45ee892 refactor: Update playhead auto-scroll buffer and remove conflicting sync 2025-06-27 00:50:37 +05:30
Deepanshu Rawat d9809c70c3 add: noopener noreferrer 2025-06-27 00:30:35 +05:30
Karan Singh 17ef810074 feat: Improve timeline playhead smooth scrolling and sync 2025-06-27 00:30:32 +05:30
Deepanshu Rawat ebb3bc89c3 remove: unused icon 2025-06-27 00:23:34 +05:30
Deepanshu Rawat 6d98c2af24 add: twitter 2025-06-27 00:17:58 +05:30
DevloperAmanSingh 0bdbd7e2b3 refactor: streamline audio track handling by consolidating clip addition logic in timeline store 2025-06-27 00:14:29 +05:30
DevloperAmanSingh 6262f2b379 refactor: introduce helper function for consistent clip naming with suffixes in timeline store 2025-06-27 00:07:47 +05:30
DevloperAmanSingh 3f0fe9d20e refactor: improve playback controls by replacing inline functions with useCallback for clip manipulation and audio separation 2025-06-27 00:07:41 +05:30
DevloperAmanSingh 0383000ada refactor: remove unused drag clip hook import in timeline clip component 2025-06-27 00:07:36 +05:30
DevloperAmanSingh a816cc503f feat: add detailed comments for clip splitting and audio separation methods in timeline store 2025-06-26 23:46:43 +05:30
DevloperAmanSingh f74bebeb8b feat: add comment for clip editing operations in timeline component 2025-06-26 23:46:38 +05:30
DevloperAmanSingh 7ca5bcfa50 feat: add comments for drag handling and resizing in timeline clip component 2025-06-26 23:46:33 +05:30
DevloperAmanSingh efdd2aa6ed feat: add dropdown menu for clip options including split and audio separation in timeline component 2025-06-26 23:41:07 +05:30
DevloperAmanSingh 12a2ec59fd feat: implement clip splitting and audio separation features with keyboard shortcuts in timeline component 2025-06-26 23:41:01 +05:30
DevloperAmanSingh b799615654 feat: enhance playback controls with keyboard shortcuts for clip manipulation and audio separation 2025-06-26 23:40:56 +05:30
DevloperAmanSingh 3224dd974a feat: add clip splitting and audio separation functionality to timeline store 2025-06-26 23:40:50 +05:30
Dipanshu RawatandGitHub 49787bdfe5 style(doc): README.md 2025-06-26 22:40:25 +05:30
priyankarpal fff95afbc6 fix: Only remove track if it becomes empty and has no other clips 2025-06-26 21:41:26 +05:30
priyankarpal f76555dae5 fix: remmove video track when media is deleted 2025-06-26 21:29:03 +05:30
priyankarpal 53184217bf fix: remove unwanted clip option menu 2025-06-26 20:28:03 +05:30
priyankarpal 1284c232a3 .gitignore modified 2025-06-26 20:26:10 +05:30
muhammed sinan b428a28aea removed unused auth-auth-form 2025-06-26 15:44:19 +05:30
SinanandGitHub 23f118969b Merge branch 'OpenCut-app:main' into main 2025-06-26 15:16:53 +05:30
izaandGitHub b10bb8ed55 Merge pull request #123 from sinanptm/fix-preview-placeholder-text
Changed Preview Empty placeholder to No media added to timeline
2025-06-26 12:46:02 +03:00
izaandGitHub d4048772ad Merge pull request #122 from DevloperAmanSingh/fix/media-panel-syntax
Fix: Media panel component
2025-06-26 12:45:43 +03:00
muhammed sinan 063094e70a changed empty placeholder to a better one 2025-06-26 14:54:49 +05:30
muhammed sinan cd88a92c6d changed empty placeholder to a better one 2025-06-26 14:53:02 +05:30
DevloperAmanSingh 70352ced92 refactor: update authentication imports in auth-form component to use signIn and signUp from OpenCut 2025-06-26 14:40:35 +05:30
DevloperAmanSingh 896f2ee554 refactor: remove unnecessary wrapper from MediaPanel component 2025-06-26 14:29:24 +05:30
izaandGitHub b66e85cf4d Merge pull request #48 from fathisiddiqi/fix/google-login
fix: set redirect uri for google login and prevent router push when login by google
2025-06-26 11:28:58 +03:00
izaandGitHub 46614d4008 Merge pull request #111 from Harshitshukla0208/fix/ai-solution-109-1750881048867
fix(issue): Address issue #109 - [FEATURE]  Refactor Component Logic by Using Custom Hooks for Cleaner Code
2025-06-26 11:28:02 +03:00
izaandGitHub 2d7f2f8503 Merge pull request #112 from Harshitshukla0208/fix/ai-solution-87-1750881155536
fix(issue): Address issue #87 - [BUG] split and delete background not visible
2025-06-26 11:27:43 +03:00
izaandGitHub 449673b79e Merge pull request #85 from priyankarpal/split-issue-83
fix: split text visible
2025-06-26 11:27:08 +03:00
izaandGitHub 4f74018648 Merge pull request #117 from anagobabatunde/fix/SQL-unknown
refactor: update imports in waitlist API and library to ensure consistent usage of drizzle-orm functions
2025-06-26 11:25:58 +03:00
Priyankar PalandGitHub 4ed9858725 Merge branch 'main' into split-issue-83 2025-06-26 11:35:55 +05:30
fathisiddiqi 2f5bde1051 fix: remove GOOGLE_REDIRECT_URI env due to better auth set the default of that 2025-06-26 09:55:30 +07:00
fathisiddiqi 6626a8c413 fix: set redirect uri for google login and prevent router push when login by google 2025-06-26 09:33:47 +07:00
ANAGO Ronnel ebb3456c10 refactor: update imports in waitlist API and library to ensure consistent usage of drizzle-orm functions 2025-06-26 02:51:17 +02:00
Maze Winther a8d6bbd03a Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-06-26 01:36:58 +02:00
Maze Winther 0f470ba7a7 fix: clip being deselected after dragging it 2025-06-26 01:36:49 +02:00
MazeandGitHub 029152dd5b Merge pull request #115 from letyassine/fixed-landing-page
fix: responsiveness of landing page
2025-06-26 01:27:47 +02:00
Maze Winther d6a2de21d0 refactor: change /public/logo.svg to exclude text and update header to use it 2025-06-26 01:08:25 +02:00
Maze Winther 53e88df0d7 refactor: remove unused imports 2025-06-26 01:02:15 +02:00
Maze Winther dd80064be6 chore: update .gitignore to include cursor files 2025-06-26 00:38:51 +02:00
Maze Winther e225272ec3 fix: some timeline issues 2025-06-26 00:37:35 +02:00
yassinehaimouch b01421f115 fix: responsiveness of landing page 2025-06-25 22:58:16 +01:00
Maze Winther 75eede20af docs: update README formatting 2025-06-25 23:47:14 +02:00
Maze Winther e23cf66373 style: introduce preview panel and move play button there 2025-06-25 23:43:10 +02:00
Maze Winther 2f3a148dd4 refactor: move debug component to separate component and place absolutely 2025-06-25 23:22:59 +02:00
Maze Winther e5fc3f9bbb style: adjust height in editor header 2025-06-25 23:09:40 +02:00
Maze Winther 0c97bc8c3f refactor: remov eunused things 2025-06-25 23:08:31 +02:00
Maze Winther 3eac1bcb0b refactor: move timeline clip into its separate component and type into /types 2025-06-25 23:05:14 +02:00
Maze Winther 3ea6b00254 fix: lock aspect ratio in preview 2025-06-25 22:55:25 +02:00
Maze Winther e7dabd1444 style: change hover effect for consistency 2025-06-25 22:52:28 +02:00
Harshit Shukla 389a546478 fix(issue): Address issue #87 - [BUG] split and delete background not visible 2025-06-26 01:22:40 +05:30
Maze Winther 4fb0d014af fix: hard-coded tailwind colors 2025-06-25 21:51:19 +02:00
Harshit Shukla c0684ca62d fix(issue): Address issue #109 - [FEATURE] Refactor Component Logic by Using Custom Hooks for Cleaner Code 2025-06-26 01:20:53 +05:30
Maze Winther ef806ceab8 refactor: use badge component from shadcn 2025-06-25 21:49:02 +02:00
Maze Winther 515be65bc4 fix: github icon being black on dark mode 2025-06-25 21:48:02 +02:00
Maze Winther 4fe0ff3010 style: update spacing in header 2025-06-25 21:44:54 +02:00
Maze Winther e1481391c7 refactor: update contributors display to show top two contributors 2025-06-25 21:43:34 +02:00
Maze Winther 777b0f7000 refactor: improved type-safety, removal of any from all instances 2025-06-25 21:40:54 +02:00
Maze Winther 926aebe004 fix: make whole clip draggable 2025-06-25 21:22:08 +02:00
Maze Winther 2775ac427d fix: remove hover effects on clip drag 2025-06-25 21:10:40 +02:00
Maze Winther f5c546d416 fix: clip getting deselected after a drag 2025-06-25 21:08:52 +02:00
Maze Winther d1e313450d fix: show clip trim handles on focus rather than hover 2025-06-25 21:06:34 +02:00
Maze Winther 27b0e51265 Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-06-25 21:02:40 +02:00
Maze Winther ebcb898e4e feat: use mouse-based dragging for clips
fixes an issue where dragging a clip would create a duplicate instead of dragging clip directly, and gives us more control

moved the drag to a hook for cleaner code
2025-06-25 21:02:34 +02:00
MazeandGitHub ffb6a35e20 Merge pull request #82 from ga1az/fix-docker-setup
Fix docker setup
2025-06-25 20:28:05 +02:00
MazeandGitHub 2ddd8cb0d7 Merge pull request #84 from ILoveScratch2/main
docs: change commands in README adapted to different platforms
2025-06-25 20:26:46 +02:00
Maze Winther baa5c43e7c feat: improve clips dragging experience 2025-06-25 19:51:50 +02:00
Maze Winther bf64ec1133 style: make join waitlist responsive 2025-06-25 19:24:05 +02:00
Maze Winther de3d3e210e fix: update heading in hero for clarity about first oss video editor 2025-06-25 19:23:01 +02:00
Maze Winther 3bb640c453 oh god (netlify) 2025-06-25 19:06:41 +02:00
Maze Winther 0f343dfbda i hate netlify 2025-06-25 18:57:52 +02:00
Maze Winther ba14d65605 Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-06-25 18:46:26 +02:00
Maze Winther 94d39dfa7e fix: netlify config 2025-06-25 18:46:22 +02:00
izaandGitHub 00d7b3ba8d Merge pull request #89 from YaoSiQian/patch-1
Changed form style of ISSUE_TEMPLATEs
2025-06-25 19:30:40 +03:00
izaandGitHub 266d3bc0a2 Merge pull request #94 from Zaid-maker/bump-bun-version
update: bump Bun version to 1.2.17 in CI workflow
2025-06-25 19:30:09 +03:00
izaandGitHub b94cc121d2 Merge pull request #107 from DevloperAmanSingh/feature/progress-bar
Feature: Add progress indicator for large file imports
2025-06-25 19:29:46 +03:00
DevloperAmanSingh 181d3fca06 fix: improve UI responsiveness during media file processing 2025-06-25 21:50:12 +05:30
Maze Winther 57bfb7610f Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-06-25 18:19:33 +02:00
Maze Winther 5977a5d253 fix: netlify config 2025-06-25 18:19:28 +02:00
DevloperAmanSingh 0723623eaf feat: add progress callback to media file processing function 2025-06-25 21:41:23 +05:30
DevloperAmanSingh f9d0be20d0 feat: implement progress tracking for media file processing in timeline 2025-06-25 21:41:16 +05:30
DevloperAmanSingh a511c57729 feat: enhance media processing with progress tracking 2025-06-25 21:41:10 +05:30
izaandGitHub 76c3293ac4 Merge pull request #103 from letyassine/new-landing-page
update: landing-page
2025-06-25 18:59:25 +03:00
izaandGitHub a969f3df86 Merge pull request #105 from sinanptm/pwa-seo
feat: Add PWA Support, metadata Improvements.
2025-06-25 18:55:26 +03:00
muhammed sinan cca25a6103 Reduce code duplication by extracting common metadata values. 2025-06-25 20:22:17 +05:30
Priyankar PalandGitHub c322ad1124 Merge branch 'main' into split-issue-83 2025-06-25 20:16:15 +05:30
muhammed sinan 2e5b623d90 metadata declared in layout file 2025-06-25 20:14:55 +05:30
muhammed sinan e786d437a5 made the project app PWA and moved metadata into a sperate file app folder 2025-06-25 19:57:35 +05:30
Maze Winther 0ae852c185 Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-06-25 16:18:00 +02:00
Maze Winther db60354349 fix: move netlify config file to root 2025-06-25 16:17:53 +02:00
yassinehaimouch ba9870a908 update: landing-page 2025-06-25 15:00:35 +01:00
Maze Winther e7d35c667f feat: add star count back to header 2025-06-25 15:36:50 +02:00
izaandGitHub 067b6e3d73 Merge pull request #96 from sinanptm/main
Pull Request: Move Type Definitions to devDependencies
2025-06-25 16:03:25 +03:00
izaandGitHub 2326faf526 Merge pull request #97 from creotove/Feat/audio-waveform
Feat: basic audio waveform added.
2025-06-25 16:01:34 +03:00
izaandGitHub 9ba57d86b6 Merge pull request #100 from DevloperAmanSingh/fix/timeline-redbar-fix
Fix: Reset playhead to start after video ends
2025-06-25 16:01:02 +03:00
izaandGitHub 92a8466368 Merge pull request #101 from GeorgeCaoJ/fix/drag-drop-new-tracks
fix(timeline): drag and drop to create new track
2025-06-25 16:00:45 +03:00
YaoSiQianandGitHub 56a8098442 fix(issue_template): fix tags 2025-06-25 20:50:33 +08:00
YaoSiQianandGitHub 6a666efa39 Merge branch 'OpenCut-app:main' into patch-1 2025-06-25 20:40:50 +08:00
GeorgeCaoJ 07351ca7e6 fix(timeline): drag and drop to create new track 2025-06-25 20:04:21 +08:00
DevloperAmanSingh d9fcef9385 fix(playback-store): reset playhead and notify video elements on completion 2025-06-25 16:34:12 +05:30
creotove 7f0bcea4ca Feat: basic audio waveform added. 2025-06-25 15:29:20 +05:30
Muhammed SinanandGitHub 903cdf5fff Merge branch 'OpenCut-app:main' into main 2025-06-25 15:01:05 +05:30
muhammed sinan 354dbbd0f9 Moved @types/pg into devDependencies 2025-06-25 14:59:49 +05:30
Zaid-maker 51a83f9f21 fix(ci): update cache key to include Bun version for consistency 2025-06-25 14:16:35 +05:00
Zaid-maker e2a0f745c8 Merge branch 'main' into bump-bun-version 2025-06-25 14:12:45 +05:00
izaandGitHub c18b173bc9 Merge pull request #86 from Shubbu03/main
UI fixes to the media-panel component
2025-06-25 12:09:47 +03:00
izaandGitHub a4201c8be6 Merge pull request #93 from DevloperAmanSingh/feat/global-mute-control
Feature - Global Mute Control
2025-06-25 12:09:05 +03:00
Zaid-maker 1722055a72 update: bump Bun version to 1.2.17 in CI workflow 2025-06-25 13:53:51 +05:00
DevloperAmanSingh 01b9dbccc2 fix(playback-store): improve mute functionality to retain previous volume state 2025-06-25 14:17:26 +05:30
YaoSiQianandGitHub d39edd8521 style(issue_template): form style 2025-06-25 16:03:36 +08:00
DevloperAmanSingh 3a6cfd73e4 feat(playback-types): add muted state and mute controls to PlaybackState and PlaybackControls interfaces 2025-06-25 13:28:04 +05:30
DevloperAmanSingh e14e137212 feat(playback-store): implement mute and unmute functionality with improved volume handling 2025-06-25 13:27:55 +05:30
DevloperAmanSingh fe1a2542cc feat(video-player): enhance video player with mute functionality and improved event handling 2025-06-25 13:27:48 +05:30
DevloperAmanSingh 66eb6f13d5 feat(preview-panel): add volume control and mute functionality to the video player 2025-06-25 13:27:39 +05:30
Shubbu03 4db515df51 code rabbit suggested fix 2025-06-25 12:29:06 +05:30
Priyankar PalGitHubcoderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
992b0cbf23 Update apps/web/src/components/editor/timeline.tsx
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-06-25 12:16:47 +05:30
Shubbu03 7444ab2894 UI fixes to the media-panel component 2025-06-25 12:13:16 +05:30
priyankarpal fd2cf591b7 fix: text visible 2025-06-25 12:01:30 +05:30
ILoveScratchandGitHub d8494185e0 docs: change commands in README adapted to different platforms 2025-06-25 14:28:56 +08:00
izaandGitHub 3989da93c3 Merge pull request #81 from pulkitgarg04/feature/project-name-editing
feature: add project name editing functionality
2025-06-25 08:31:24 +03:00
ga1az 10d11a5baa Merge branch 'main' of github.com:ga1az/OpenCut into fix-docker-setup 2025-06-25 01:22:09 -04:00
ga1az 998ee086e5 chore(docker): update Dockerfile to set NODE_ENV to production and ensure telemetry is disabled 2025-06-25 01:21:09 -04:00
izaandGitHub 402b004ef3 Merge pull request #71 from Harshitshukla0208/fix/ai-solution-67-1750807491754
fix(issue): Address issue #67 - [BUG] buttons jitter left and right
2025-06-25 08:02:04 +03:00
izaandGitHub affa239f7b Merge pull request #72 from Harshitshukla0208/fix/ai-solution-65-1750807569933
fix(issue): Address issue #65 - [BUG]
2025-06-25 08:00:57 +03:00
izaandGitHub 151b3dfc51 Merge pull request #74 from letyassine/fix-og-image
fix: metadata for og image
2025-06-25 08:00:07 +03:00
izaandGitHub 4a8ff91ae3 Merge pull request #76 from creotove/main
Fix: [BUG] buttons jitter left and right #67
2025-06-25 07:59:11 +03:00
Pulkit Garg 411a9a5f1c feature: add project name editing functionality 2025-06-25 10:26:29 +05:30
izaandGitHub b8057cdd30 Merge pull request #77 from ga1az/fix-docker-setup
refactor(docker): update Dockerfile and docker-compose
2025-06-25 07:56:14 +03:00
izaandGitHub e01142b798 Merge pull request #80 from pulkitgarg04/fix/drizzle-schema-path-configuration
fix: drizzle schema path configuration in web app
2025-06-25 07:55:40 +03:00
Pulkit Garg 7461b763b1 fix: drizzle schema path configuration in web app 2025-06-25 10:09:42 +05:30
ga1az 1f2c505efe chore(docker): enhance Dockerfile by adding dependency installation step and removing redundant NODE_ENV variable 2025-06-24 23:59:42 -04:00
ga1az 547f566fc2 refactor(docker): update Dockerfile and docker-compose for improved build process and add health check API 2025-06-24 23:44:06 -04:00
creotove 7de09adce6 Fix: unwanted seperator when there is a clip on track. 2025-06-25 08:51:17 +05:30
creotove 84d206c01e Fix: [BUG] buttons jitter left and right #67 2025-06-25 08:36:27 +05:30
yassinehaimouch 22ae5d0e37 fix: metadata for og image 2025-06-25 01:16:37 +01:00
MazeandGitHub bc28ce09cb Merge pull request #69 from letyassine/og
add: opengraph image
2025-06-25 01:31:11 +02:00
Harshit Shukla a9293ffb48 fix(issue): Address issue #65 - [BUG] 2025-06-25 04:56:15 +05:30
Harshit Shukla 274d03185b fix(issue): Address issue #67 - [BUG] buttons jitter left and right 2025-06-25 04:54:56 +05:30
yassinehaimouch 9bb19712e5 add: opengraph image 2025-06-24 23:27:00 +01:00
Maze Winther bc3fbabfb2 fix(header): remove target attribute from 'Editor' link for consistency 2025-06-24 22:49:19 +02:00
Maze Winther 9354d69acf feat(header): conditionally render 'Editor' or 'GitHub' button based on environment 2025-06-24 22:48:55 +02:00
Maze Winther f8a4cd4ac6 fix(hero): remove padding from hero waitlist count 2025-06-24 22:43:43 +02:00
Maze Winther 5fc205fc57 fix(timeline): update drag overlay styling for better visibility 2025-06-24 22:23:02 +02:00
Maze Winther 8f583c25e0 feat(timeline): add context menu handling to TimelineTrackContent component 2025-06-24 22:20:17 +02:00
izaandGitHub 27471ba532 Merge pull request #63 from OpenCut-app/izadoesdev-patch-1
Update README.md
2025-06-24 22:40:35 +03:00
izaandGitHub 1d13e9a67f Update README.md 2025-06-24 22:40:19 +03:00
izaandGitHub 9b29122a2c Merge pull request #58 from creotove/main
Fix: [BUG] Backend not running #56.
2025-06-24 22:34:58 +03:00
izaandGitHub dcbae4554c Merge pull request #60 from karansingh21202/fix/hero-section-spacing
fix(hero): improve spacing and prevent footer overlap with email section in landing page
2025-06-24 22:33:37 +03:00
Maze Winther 33916d4e91 chore: update @types/node to version 22.15.33 and add project name in package.json 2025-06-24 21:29:15 +02:00
Maze Winther 595f4c5f88 refactor: update header component to replace 'Start editing' button with 'GitHub' 2025-06-24 20:58:37 +02:00
Karan Singh 77560dc5aa fix(hero): improve spacing and prevent footer overlap in hero section (#51) 2025-06-25 00:08:13 +05:30
creotove e8a22d99cb Fix: [BUG] Backend not running #56. 2025-06-24 22:32:37 +05:30
izaandGitHub 71be89ada3 Merge pull request #55 from Zaid-maker/fix-workflow
Fix the CI by passing required env
2025-06-24 19:22:21 +03:00
Zaid-maker afb32909fe re arrange 2025-06-24 21:19:31 +05:00
Zaid-maker 5ed2d729bf fixing CI 2025-06-24 21:16:36 +05:00
Maze Winther bbf9aae234 Revert "refactor: overhaul timeline component, separate into different files"
This reverts commit 2aa37920d5.
2025-06-24 17:54:41 +02:00
Maze Winther 2aa37920d5 refactor: overhaul timeline component, separate into different files 2025-06-24 17:51:34 +02:00
Maze Winther c3df43b8c8 Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-06-24 17:49:39 +02:00
Maze Winther 91251fa94d chore: update Netlify configuration for build process and publishing path 2025-06-24 17:49:12 +02:00
izaandGitHub c0b85b0f29 Merge pull request #43 from Zaid-maker/implement-workflows
feat: add Bun CI workflow for building and testing
2025-06-24 18:23:07 +03:00
izaandGitHub b17e05f126 Merge pull request #45 from GeorgeCaoJ/feat/media-panel-filter
feat: support filter media type in media panel
2025-06-24 18:22:29 +03:00
izaandGitHub 792ebcef38 Merge pull request #47 from Jeanpseven/main
Update waitlist.ts
2025-06-24 18:21:00 +03:00
Maze Winther e5ade1f3ac style: remove roundec corners from tracks 2025-06-24 17:14:15 +02:00
Maze Winther 89a003635c refactor: remove comments and unused stuff 2025-06-24 16:12:39 +02:00
Maze Winther 61771f2a3e style: remove gradient from contributors heading 2025-06-24 15:35:42 +02:00
Maze Winther 65b8934773 feat: add contributors page to showcase OpenCut contributors and their contributions 2025-06-24 15:11:30 +02:00
Maze Winther 6581218ea9 fix: update hero text for clarity and platform compatibility 2025-06-24 15:10:55 +02:00
Zaid-maker 82e7d2236d Merge branch 'main' into implement-workflows 2025-06-24 17:57:40 +05:00
MazeandGitHub 20bb485ad1 Merge branch 'main' into main 2025-06-24 14:48:48 +02:00
Maze Winther 4b2afb7271 style: set min/max height for timeline 2025-06-24 14:19:47 +02:00
GeorgeCaoJ 25dc5f5365 fix: add missing semicolon 2025-06-24 20:14:07 +08:00
W r e n c handGitHub d929caabff Update waitlist.ts 2025-06-24 09:13:42 -03:00
GeorgeandGitHub 984be4f6e6 Merge branch 'OpenCut-app:main' into feat/media-panel-filter 2025-06-24 20:06:08 +08:00
GeorgeCaoJ 4941ec4798 feat: support filter media in media panel 2025-06-24 20:02:36 +08:00
Zaid-maker 62051de6c1 Merge branch 'main' into implement-workflows 2025-06-24 16:59:44 +05:00
Maze Winther 9e761a9c0b style: clean up timeline 2025-06-24 13:58:38 +02:00
Maze Winther 263c9fb5a0 Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-06-24 13:53:14 +02:00
Maze Winther 3dff7e37d0 refactor: update database imports to use new package structure 2025-06-24 13:52:48 +02:00
Zaid-maker 7c83b8e70b fix: update Bun setup action to a specific commit version 2025-06-24 16:48:10 +05:00
Zaid-maker 5de5444a3e fix: correct cache key for Bun modules in CI workflow 2025-06-24 16:32:35 +05:00
Zaid-maker 642c11fc9e implement cache 2025-06-24 16:31:13 +05:00
Zaid-maker 6a6ba8d0ae Add a top-level concurrency block so obsolete runs are auto-cancelled: 2025-06-24 16:29:40 +05:00
Zaid-maker 6a5956e64b feat: enable multi-platform builds in Bun CI workflow 2025-06-24 16:21:51 +05:00
Zaid-maker 729d1761f8 feat: add Bun CI workflow for building and testing 2025-06-24 16:14:40 +05:00
Hyteq 900deab065 fix nextjs issue with hoisted dependencies 2025-06-24 13:45:09 +03:00
Hyteq b57d6db5a0 first test 2025-06-24 13:44:23 +03:00
izaandGitHub 46b8929a70 Merge pull request #41 from GeorgeCaoJ/main
fix: support context menu on track name and fix bugs of zooming in timeline track content
2025-06-24 12:34:19 +03:00
GeorgeCaoJ 5bba941dbc fix: wheel zooming in timeline tracks content 2025-06-24 17:22:05 +08:00
Maze Winther e9889b8fa7 feat: update authentication imports and migrate to new db package structure 2025-06-24 11:05:20 +02:00
Maze Winther 0ebab57aaa fix: version mismatch with drizzle 2025-06-24 10:55:06 +02:00
Hyteq 33637c0481 Merge branch 'main' of https://github.com/OpenCut-app/OpenCut 2025-06-24 11:31:41 +03:00
Maze Winther c3d49711b0 Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-06-24 10:32:22 +02:00
Maze Winther b9e1dcf23e refactor: change button variant from 'ghost' to 'text' across multiple components for consistency 2025-06-24 10:32:19 +02:00
Hyteq 3b4577e474 feat: initial auth package 2025-06-24 11:31:35 +03:00
Hyteq 30bc2127a2 Merge branch 'main' of https://github.com/OpenCut-app/OpenCut 2025-06-24 11:29:18 +03:00
Hyteq d25e1eaa1d feat: migrated packages properly, using packages in web app 2025-06-24 11:29:15 +03:00
Hyteq e2872005fb feat: initial auth package 2025-06-24 11:27:19 +03:00
Hyteq a21fc92718 update deps for db package 2025-06-24 11:25:59 +03:00
Hyteq 140880a444 chore(turborepo): initial setup and config, move db to package 2025-06-24 11:23:40 +03:00
izaandGitHub 75ee0fb637 Update README.md 2025-06-24 11:10:58 +03:00
izaandGitHub e9aeb8a63b Merge pull request #42 from YaoSiQian/patch-1
style(README): insert logo in header
2025-06-24 11:08:45 +03:00
YaoSiQianandGitHub f0bdf838af style(README): insert logo in header 2025-06-24 15:54:31 +08:00
GeorgeCaoJ 503e738617 feat: add context menu in tracks and zooming in timeline 2025-06-24 14:25:29 +08:00
Hyteq 65bff0d2da fix: merge conflicts, merge 2025-06-24 08:22:24 +03:00
izaandGitHub 612d0d8c15 Merge pull request #40 from pulkitgarg04/main
feat: Enable Playhead Dragging for Video Navigation
2025-06-24 08:22:20 +03:00
Hyteq e29e0892ac debug logic for the overlaying / active clips on preview 2025-06-24 08:21:36 +03:00
Pulkit GargandGitHub f0e305cfe6 Merge branch 'main' into main 2025-06-24 10:49:15 +05:30
Hyteq dc35619017 clean up and simplifying more logic 2025-06-24 08:10:11 +03:00
izaandGitHub aa6d42c517 Merge pull request #23 from aashishparuvada/main
assigned-actions-to-work-on-selected-clips and added-undo/redo-functionality
2025-06-24 08:03:32 +03:00
izaandGitHub ad4531e151 Merge pull request #35 from letyassine/hero-landing-page
fix: hero landing page height
2025-06-24 08:00:58 +03:00
izaandGitHub 973ee68ae2 Merge pull request #25 from andrewckor/playhead-browse
Implement click-to-seek functionality in timeline
2025-06-24 08:00:20 +03:00
Pulkit Garg e664ea0271 feat: Enable Playhead Dragging for Video Navigation 2025-06-24 10:28:58 +05:30
Hyteq 55c95cd574 Merge branch 'main' of https://github.com/OpenCut-app/OpenCut 2025-06-24 07:56:57 +03:00
izaandGitHub 699af1aefd Merge pull request #28 from dipanshurdev/minor-feat
add: github stars count
2025-06-24 07:57:45 +03:00
izaandGitHub e367d8960d Merge pull request #24 from 3raphat/fix/auth-url
fix: update auth routes
2025-06-24 07:56:56 +03:00
Hyteq aacf4c5979 fix: update login url in middleware 2025-06-24 07:56:52 +03:00
Hyteq ad0476e78f fix: merge conflicts & updated more URLs following our new path 2025-06-24 07:55:02 +03:00
izaandGitHub 326da414d2 Merge pull request #26 from SamSyntax/main
Playhead timeline dragging/scrubbing
2025-06-24 07:53:50 +03:00
izaandGitHub 42c493af4b Merge pull request #32 from anagobabatunde/main
docs: expand setup instructions with prerequisites and database setup steps
2025-06-24 07:51:58 +03:00
izaandGitHub 69ab018f66 Merge branch 'main' into main 2025-06-24 07:51:51 +03:00
izaandGitHub 704f620c92 Merge pull request #31 from YaoSiQian/patch-1
Fix Docker stuff (with README guidance)
2025-06-24 07:50:06 +03:00
izaandGitHub 40a0d87c44 Merge branch 'main' into patch-1 2025-06-24 07:49:37 +03:00
aashishparuvada 073f0d89bd feat:enabled-redo-functionality 2025-06-24 09:31:53 +05:30
aashishparuvada 9570847edd updated package-lock.json 2025-06-24 09:27:13 +05:30
aashishparuvada 5eb29bb01d feat:implemented-undo-feature-for-timeline 2025-06-24 09:26:31 +05:30
Aashish ParuvadaandGitHub bd34c32ec1 Merge branch 'OpenCut-app:main' into main 2025-06-24 08:17:12 +05:30
Maze Winther 4260d4be08 feat: implement unified AuthForm component for login and signup pages 2025-06-24 02:48:14 +02:00
Maze Winther 6d29228c8c style: adjust margin in hero section for improved layout 2025-06-24 02:45:59 +02:00
Maze Winther 2b06fd97d6 fix: 404 on auth pages 2025-06-24 02:20:41 +02:00
Maze Winther b5e8e8ce62 chore: update favicon.ico for improved branding 2025-06-24 02:19:11 +02:00
Maze Winther c64480ac79 chore: update logo image in public assets 2025-06-24 02:15:00 +02:00
Maze Winther 37ceaaed5e docs: update README with contributing guidelines and quick start instructions for contributors 2025-06-24 02:09:43 +02:00
Maze Winther f2879be973 docs: enhance contributing guidelines with detailed setup instructions and environment variable configuration 2025-06-24 01:55:40 +02:00
Maze Winther 5bff26d3e5 Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-06-24 01:49:12 +02:00
Maze Winther 69566ffa83 fix: update hero section text from "CapCut alternative" to "video editor" 2025-06-24 01:48:46 +02:00
Maze Winther 63b698fe12 chore: add .cursorignore file to exclude specific files from cursor tracking 2025-06-24 01:48:06 +02:00
yassinehaimouch bc4c064ad6 fix: hero landing page height 2025-06-23 23:01:50 +01:00
YaoSiQianandGitHub 79ac2c2823 style(README): hot fix at line break 2025-06-24 05:06:21 +08:00
YaoSiQianandGitHub b772c6f68e fix(docker): web service 2025-06-24 05:00:34 +08:00
babs a3e6585306 docs: expand setup instructions with prerequisites and database setup steps 2025-06-23 20:59:31 +00:00
Dipanshu Rawat 57e42e2c45 ref: fetch api function 2025-06-24 02:06:55 +05:30
Dipanshu Rawat fb9c560c5b add: github stars count by API 2025-06-24 01:40:46 +05:30
Andrew Kordampalos c7f56d61d7 Format 2025-06-23 22:41:38 +03:00
Andrew Kordampalos 267a590d04 De-duplicate code 2025-06-23 22:32:17 +03:00
SamSyntax 1c6154999d Adding comments 2025-06-23 21:31:08 +02:00
SamSyntaxandGitHub 15800dae92 Update media-processing.ts
Removing console log of the file
2025-06-23 21:17:52 +02:00
Andrew Kordampalos 2a1ac8fcf7 Implement click-to-seek functionality in timeline 2025-06-23 22:17:36 +03:00
SamSyntaxandGitHub 09a0e10b91 Update header.tsx
Removing dev change
2025-06-23 21:16:47 +02:00
SamSyntax 59ca411c53 Implementing playhead scrubbing over timeline 2025-06-23 21:13:18 +02:00
Aashish ParuvadaandGitHub 5322ab9907 Merge branch 'OpenCut-app:main' into main 2025-06-24 00:34:51 +05:30
3raphat f959ba87e2 fix: update auth routes 2025-06-24 01:38:53 +07:00
aashishparuvada 360fdffa76 hotfix:disabled-vertical-scrolling-in-timeline 2025-06-23 23:53:18 +05:30
aashishparuvada f17d848b17 feat:assigned-actions-for-selected-clips 2025-06-23 23:48:15 +05:30
Hyteq a6a520db70 fix: rename auth folder to (auth) to prevent route, cleaner to not have /auth prefix 2025-06-23 21:07:58 +03:00
MazeandGitHub c691835d85 Merge pull request #22 from DeepanshuMishraa/changes
Added google auth and some auth page ui optimisations
2025-06-23 19:58:54 +02:00
izaandGitHub 459b530aee Merge pull request #20 from Shubbu03/main
Completed the getVideoInfo func according to the comment specified
2025-06-23 20:56:48 +03:00
aashishparuvada 4a9b102ce1 feature:deselect-clips-on-click 2025-06-23 23:26:41 +05:30
Deepanshu Mishra 8bcd226bdb Added google auth and some auth page ui optimisations 2025-06-23 22:51:34 +05:30
aashishparuvada d605cd762c hotfix:fixed-cmd-for-deselection 2025-06-23 22:49:56 +05:30
Shubbu03 1a239b535f Merge branch 'main' of https://github.com/Shubbu03/OpenCut 2025-06-23 22:25:47 +05:30
Shubbu03 6a701ee8c0 better error handling 2025-06-23 22:24:57 +05:30
Shubbu03 327196356c code-rabbit suggested fixes 2025-06-23 22:22:43 +05:30
Shubbu03 7d1b5ceb0d Completed the getVideoInfo func according to the comment specified 2025-06-23 22:10:15 +05:30
izaandGitHub fddbecc736 Merge pull request #18 from aashishparuvada/main
enabled-multiclip-selection-and-deselection
2025-06-23 19:10:18 +03:00
aashishparuvada e1ffb97be6 hotfix:removed-redundant-div 2025-06-23 21:24:51 +05:30
aashishparuvada b9f4aebfd3 Merge branch 'main' of https://github.com/aashishparuvada/OpenCut
merged main
2025-06-23 21:17:25 +05:30
aashishparuvada f44f0acf70 hotfix-improved-selection-standards 2025-06-23 21:16:21 +05:30
Aashish ParuvadaandGitHub 4c8a37c635 Merge branch 'OpenCut-app:main' into main 2025-06-23 21:06:33 +05:30
aashishparuvada 630250c23b enabled-multiclip-selection 2025-06-23 20:51:36 +05:30
izaandGitHub 13817a5ff8 Merge pull request #15 from StarKnightt/feature/speed-control
Add video speed control and improve playback smoothness
2025-06-23 17:53:13 +03:00
Hyteq 9c61b72bd4 fix conflict 2025-06-23 17:51:20 +03:00
izaandGitHub c8f92a76e3 Merge pull request #16 from aashishparuvada/main
enabled-clip-selection-and-deletion
2025-06-23 17:49:20 +03:00
Maze Winther 2cad7f9c3c feat: add waitlist table and migration for tracking user signups 2025-06-23 16:44:07 +02:00
Hyteq 14a720256f Merge branch 'main' of https://github.com/OpenCut-app/OpenCut 2025-06-23 17:43:49 +03:00
Hyteq 0f7217f42d dik 2025-06-23 17:43:44 +03:00
aashishparuvada 12d26c574b enabled-clip-selection 2025-06-23 20:05:49 +05:30
StarKnightt f72f5c100b Add video speed control and improve playback smoothness
Added a speed control dropdown in the timeline toolbar that lets users change video playback speed between 0.5x to 2x. The dropdown shows the current speed and offers preset options.

Made video playback smoother by:
- Using better timing for speed changes
- Improving video synchronization
- Reducing playback stutters
- Making speed changes more responsive

The speed control is now easily accessible while editing and works smoothly with all video clips.
2025-06-23 20:01:12 +05:30
Maze Winther 054c68140c feat: add redirect from appcut to opencut in Netlify configuration 2025-06-23 16:25:06 +02:00
Hyteq 294ba01abe feat: initial state for composition and overlay editing on the video player 2025-06-23 15:39:56 +03:00
Hyteq 61c172c9cc feat: proper video playback including images and videos on the timeline 2025-06-23 14:40:34 +03:00
Hyteq 7b6ab8f152 chore: cleanup 2025-06-23 14:21:42 +03:00
Hyteq c8e522a82f fix: items duplicate instead of moving t racks 2025-06-23 14:19:56 +03:00
Hyteq 3540226af5 fix: clips duplicating when moving them around 2025-06-23 14:14:06 +03:00
Hyteq b7ccce954e fix: clips duplicating when moving them around 2025-06-23 14:05:14 +03:00
Hyteq c55b7796a6 fix: package json commands 2025-06-23 13:40:14 +03:00
Hyteq fd7ac5fd42 fix: mentions of npm 2025-06-23 13:39:38 +03:00
Hyteq a71ca538b1 fix: security emmai;l 2025-06-23 13:38:08 +03:00
Hyteq 0a4d5186a7 feat: github templates and db migration 2025-06-23 13:37:37 +03:00
Hyteq 8ab6d243bb feat: github templates and db migration 2025-06-23 13:37:33 +03:00
Hyteq 654c031380 feat: initial PR template 2025-06-23 13:33:28 +03:00
izaandGitHub c7d4abbe05 Merge pull request #6 from Sompalkar/fix/timeline-clip-deletion
fix: delete only selected clip from timeline
2025-06-23 13:24:18 +03:00
izaandGitHub ff9b48d6e8 Merge pull request #4 from 3raphat/update-env
chore(env): update Next Auth vars to Better Auth
2025-06-23 13:03:05 +03:00
izaandGitHub 7c5057ebd7 Merge pull request #5 from ShadowArcanist/main
feat: Add healthchecks to docker compose
2025-06-23 13:01:39 +03:00
izaandGitHub 0555d2893d Update docker-compose.yaml 2025-06-23 13:01:08 +03:00
Sompalkar c336847bf7 fix: delete only selected clip from timeline 2025-06-23 15:20:59 +05:30
Sompalkar 9abb502919 fix: delete only selected clip from timeline 2025-06-23 14:43:12 +05:30
ShadowArcanist cbe152e83d feat: add healthchecks to docker compose 2025-06-23 14:42:00 +05:30
3raphatandGitHub c94700b069 Merge branch 'main' into update-env 2025-06-23 15:53:45 +07:00
MazeandGitHub d92aed2dac Merge pull request #1 from yimothysu/main
Add /auth/login and /auth/signup routes
2025-06-23 10:52:40 +02:00
MazeandGitHub d8917f2632 Merge pull request #2 from 3raphat/feat/redis-compose
feat: add redis to docker compose
2025-06-23 10:52:30 +02:00
3raphat befec3131b chore(env): update Next Auth vars to Better Auth 2025-06-23 15:51:49 +07:00
MazeandGitHub f940873bbf Merge pull request #3 from Ni-zav/docs/update-readme
docs: update readme, add project structure and bun commands
2025-06-23 10:50:34 +02:00
Nizav 315446c9eb docs: update readme, add project structure and bun commands 2025-06-23 15:33:06 +07:00
3raphat 084ec47249 feat: add redis to docker compose 2025-06-23 15:12:34 +07:00
Hyteq e45e7e9f9d fix: old AppCut name references 2025-06-23 10:35:07 +03:00
Hyteq 56480772c3 feat: initial DnD and ffmpeg install 2025-06-23 10:33:03 +03:00
Hyteq 56efb55a0f cleanup more edge cases, disable overlaying items on the same track 2025-06-23 09:52:26 +03:00
Hyteq e57618a08f feat: cleanup timeline further, easier to view durations / view larger videos, design makes more sense, more responsive 2025-06-23 09:44:07 +03:00
Hyteq 50931c487b fix: better drag and drop gestures, animation, more robust handling, easier movements 2025-06-23 09:40:31 +03:00
Hyteq f688c7ef5d fix: video player properly reflecting the edited timeline, video start 2025-06-23 09:35:32 +03:00
Hyteq 68864c1442 feat: cleaned up logic for timeline, allow moving videos / items around the timeline instead of a fixed flexbox 2025-06-23 09:31:57 +03:00
Hyteq d3207e6745 feat: trim videos properly, draggable trim edges to both sides, cleaned up logic and optimized 2025-06-23 09:25:27 +03:00
Hyteq ed6ab6cd5b feat: cleaned up video player, added functionality, zooming in/out, timeline position viewer, keyboard controls 2025-06-23 09:18:15 +03:00
Hyteq 589f4a20a1 feat: migrated to latest radix to improve performance / tree-shaking 2025-06-23 09:06:42 +03:00
Hyteq 79b8275bc0 chore: cleaned up annoying toasts that arent needed 2025-06-23 09:04:33 +03:00
Hyteq c2b70c13e9 chore: cleaned up logic, comments, logic and overall structure of media panel to be more robust 2025-06-23 09:02:32 +03:00
Hyteq 2bb347f1ae feat: added biome, fixed input hydration issue from uncontrolled input being used as controlled input 2025-06-23 08:55:55 +03:00
Hyteq e73e3506fc feat: add docker-compose for local database setup, example .env 2025-06-23 08:52:37 +03:00
jimsu 54235d8dfe Add /auth/login and /auth/signup routes 2025-06-22 22:32:33 -07:00
Maze Winther 8e519ca7e1 fix: github link 2025-06-23 00:03:23 +02:00
Maze Winther 3c8d5a25e8 fix: change github links 2025-06-22 22:30:40 +02:00
Maze Winther ead6684b08 change app name from AppCut to OpenCut 2025-06-22 22:22:08 +02:00
Maze Winther d1b3eae895 fix: remove toast success for reordering 2025-06-22 22:19:47 +02:00
Maze Winther 8d773086c5 Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-06-22 22:10:52 +02:00
Maze Winther 7e3a80eb74 improve lots of stuff around the editor 2025-06-22 22:10:50 +02:00
Maze Winther ce49c5ff5f new README 2025-06-22 22:07:06 +02:00
izaandGitHub 620cfeea8b Update layout.tsx 2025-06-22 22:14:40 +03:00
Maze Winther 4148308b66 update databuddy script 2025-06-22 20:43:36 +02:00
Maze Winther 6ee16f9df8 feat: so much stuff 2025-06-22 19:28:03 +02:00
Maze Winther e22aa6620c add databuddy script 2025-06-22 18:39:59 +02:00
Maze Winther 95f3db8d72 fix: don't cache waitlist count 2025-06-22 18:05:44 +02:00
Maze Winther 850c62549a fix LICENSE 2025-06-22 17:11:31 +02:00
Maze Winther 4f30cd734a docs: add license 2025-06-22 15:23:11 +02:00
Maze Winther 49705851cf add MIT license 2025-06-22 15:22:29 +02:00
Maze Winther cb2a7484ed add drag and drop to the timeline 2025-06-22 14:50:22 +02:00
Maze Winther 6c8d6d99a9 fix: remove unncessary open editor button from header 2025-06-22 14:42:14 +02:00
Maze Winther afa52384ca fix: redirect in middleware if not authenticated 2025-06-22 14:41:08 +02:00
Maze Winther e70ac78475 feat: use zod for validation 2025-06-22 14:39:04 +02:00
Maze Winther f2f45f6532 some SEO 2025-06-22 14:31:41 +02:00
Maze Winther c1e5fe488b feat: new favicon 2025-06-22 14:31:01 +02:00
Maze Winther 356c0cc549 feat: add logo to header 2025-06-22 14:28:34 +02:00
Maze Winther 4bcd8aec0e chore: add cross-env as dep 2025-06-22 14:27:44 +02:00
Maze Winther c535aeed30 feat: new logo 2025-06-22 14:27:36 +02:00
Maze Winther 0ebf4edd45 feat: redirect /editor to / 2025-06-22 14:26:03 +02:00
Maze Winther e84caf3f9f feat: add waitlist signup count 2025-06-22 14:25:19 +02:00
Maze Winther 6e84dcaffb chore: add dotenv as dependency 2025-06-22 13:45:34 +02:00
Maze Winther 39edd55119 netlify file 2025-06-22 13:26:17 +02:00
Maze Winther faca97f1ae add upstash rate limiting 2025-06-22 13:21:03 +02:00
Maze Winther ceb253f585 ui: change button to waitlist 2025-06-22 13:12:00 +02:00
Maze Winther 5792eecfc3 finish renaming 2025-06-22 13:09:21 +02:00
Maze Winther 6b77e83ca0 rename app provider to editor provider and add it to the editor instead 2025-06-22 13:08:37 +02:00
Maze Winther be97024868 so much stuff??? 2025-06-22 13:07:02 +02:00
Maze Winther f2d27e578e move editor from home to /editor 2025-06-22 10:05:33 +02:00
Maze Winther a0ca5bf703 format tsconfig 2025-06-22 10:05:01 +02:00
Maze Winther f834e39afd update README 2025-06-22 10:03:34 +02:00
Maze Winther 87a83a5604 Initial commit 2025-06-22 10:02:50 +02:00
490 changed files with 21893 additions and 43991 deletions
File diff suppressed because it is too large Load Diff
-35
View File
@@ -1,35 +0,0 @@
---
alwaysApply: true
---
# Comment Guidelines
## Good Comments (Human-style)
- Explain WHY, not WHAT
- Document non-obvious behavior or edge cases
- Warn about performance implications or side effects
- Explain business logic that isn't clear from code
Examples:
```javascript
// transfer, not copy; sender buffer detaches
// satisfies: check shape; keep literals
// keep multibyte across chunks
// timingSafeEqual throws on length mismatch
```
## Bad Comments (AI-style)
- Don't explain what the code literally does
- Don't add changelog-style comments in code
- Don't comment every line or obvious operations
Avoid:
```javascript
// Prevent duplicate initialization
// Check if project is already loaded
// Mark as initializing to prevent race conditions
// (changed from blah to blah)
```
## Rule
Only add comments when there's genuinely non-obvious behavior, performance considerations, or business logic that needs context. Code should be self-documenting through naming and structure.
-21
View File
@@ -1,21 +0,0 @@
---
alwaysApply: true
---
# Handling Uncertainty
## Principle
If you can't confidently respond due to missing context, data access, or ambiguity (multiple interpretations), report it instead of guessing. Seek clarification to avoid errors.
Apply when: query lacks details, no access to info/tools, or unclear intent.
## How to Report
1. **Description**: Why uncertain and what you need.
2. **Questions**: 1-3 targeted ones.
3. **Assumptions** (opt.): State if proceeding; omit otherwise.
Direct and concise.
**Assumptions**: None.
Builds transparency.
-9
View File
@@ -1,9 +0,0 @@
---
alwaysApply: true
---
# Readability First
Optimize code for AI agents to understand and modify.
Never abbreviate. `event` not `e`, `element` not `el`. If it's easy to read, it's correct. In this case, "config" is better than "configuration" because it's shorter and is **still very readable**. "El" is not very readable.
-52
View File
@@ -1,52 +0,0 @@
---
alwaysApply: true
---
# Separation of Concerns
## Core Principle
Each file should have one single purpose/responsibility. Related functionality should be grouped together, unrelated functionality should be separated.
## Good Separation
- One file per major concern (auth, validation, data transformation)
- Group related utilities together
- Extract shared logic into dedicated files
- Keep API routes focused on their specific endpoint logic
Examples:
```javascript
// ✅ Good: Each file has clear responsibility
/lib/rate-limit.ts // Rate limiting utilities
/lib/validation.ts // Input validation schemas
/lib/freesound-api.ts // External API integration
/api/sounds/search/route.ts // Route handler only
```
## Bad Mixing of Concerns
Avoid cramming multiple responsibilities into one file:
```javascript
// ❌ Bad: Route file doing everything
/api/sounds/search/route.ts
- Rate limiting logic
- Validation schemas
- API transformation
- External API calls
- Response formatting
- Error handling utilities
```
## When to Separate
- File is getting long (>500 lines)
- Multiple distinct responsibilities in one file
- Logic could be reused elsewhere
- Complex utilities that distract from main purpose
## Rule
One file, one responsibility. Extract shared concerns into focused utility files
-105
View File
@@ -1,105 +0,0 @@
---
description: Ultracite Rules - AI-Ready Formatter and Linter
globs: "**/*.{ts,tsx,js,jsx}"
alwaysApply: true
---
# Project Context
Ultracite enforces strict type safety, accessibility standards, and consistent code quality for JavaScript/TypeScript projects using Biome's formatter.
## Key Principles
- Zero configuration required
- Subsecond performance
- Maximum type safety
- AI-friendly code generation
## Before Writing Code
1. Analyze existing patterns in the codebase
2. Consider edge cases and error scenarios
3. Follow the rules below strictly
4. Validate accessibility requirements
5. Avoid code duplication
## Rules
### Accessibility (a11y)
- Always include a `title` element for icons unless there's text beside the icon.
- Always include a `type` attribute for button elements.
- Accompany `onClick` with at least one of: `onKeyUp`, `onKeyDown`, or `onKeyPress`.
- Accompany `onMouseOver`/`onMouseOut` with `onFocus`/`onBlur`.
### Code Complexity and Quality
- Don't use primitive type aliases or misleading types.
- Don't use the comma operator.
- Use for...of statements instead of Array.forEach.
- Don't initialize variables to undefined.
- Use .flatMap() instead of map().flat() when possible.
### React and JSX Best Practices
- Don't import `React` itself.
- Don't use both `children` and `dangerouslySetInnerHTML` props on the same element.
- Don't insert comments as text nodes.
- Use `<>...</>` instead of `<Fragment>...</Fragment>`.
### Function Parameters and Props
- Always use destructured props objects instead of individual parameters in functions.
- Example: `function helloWorld({ prop }: { prop: string })` instead of `function helloWorld(param: string)`.
- This applies to all functions, not just React components.
### Correctness and Safety
- Don't assign a value to itself.
- Avoid unused imports and variables.
- Don't use await inside loops.
- Don't hardcode sensitive data like API keys and tokens.
- Don't use the TypeScript directive @ts-ignore.
- Make sure the `preconnect` attribute is used when using Google Fonts.
- Don't use the `delete` operator.
- Don't use `require()` in TypeScript/ES modules - use proper `import` statements.
### TypeScript Best Practices
- Don't use TypeScript enums.
- Use either `T[]` or `Array<T>` consistently.
- Don't use the `any` type.
### Style and Consistency
- Don't use global `eval()`.
- Use `String.slice()` instead of `String.substr()` and `String.substring()`.
- Don't use `else` blocks when the `if` block breaks early.
- Put default function parameters and optional function parameters last.
- Use `new` when throwing an error.
- Use `String.trimStart()` and `String.trimEnd()` over `String.trimLeft()` and `String.trimRight()`.
### Next.js Specific Rules
- Don't use `<img>` elements in Next.js projects.
- Don't use `<head>` elements in Next.js projects.
## Example: Error Handling
```typescript
// ✅ Good: Comprehensive error handling
try {
const result = await fetchData();
return { success: true, data: result };
} catch (error) {
console.error("API call failed:", error);
return { success: false, error: error.message };
}
// ❌ Bad: Swallowing errors
try {
return await fetchData();
} catch (e) {
console.log(e);
}
```
-53
View File
@@ -1,53 +0,0 @@
---
alwaysApply: true
---
# Scannable Code Guidelines/Separating Concerns.
## Core Principle
Code should be scannable through proper abstraction, not comments. Use variables and helper functions to make intent clear at a glance.
## Good Scannable Code
- Extract complex logic into well-named variables
- Create helper functions for multi-step operations
- Use descriptive names that explain intent
Examples:
```javascript
// ✅ Scannable: Intent is clear from variable names
const isValidUser = user.isActive && user.hasPermissions;
const shouldProcessPayment = amount > 0 && !order.isPaid;
// ✅ Scannable: Complex logic extracted to helper
const searchParams = buildFreesoundSearchParams({ query, filters, pagination });
const transformedResults = transformFreesoundResults({ rawResults });
```
## Bad Unscannable Code
Avoid:
```javascript
// ❌ Hard to scan: What does this condition mean?
if (type === "effects" || !type) {
params.append("filter", "duration:[* TO 30.0]");
params.append("filter", `avg_rating:[${min_rating} TO *]`);
if (commercial_only) {
params.append("filter", 'license:("Attribution" OR "Creative Commons 0")');
}
}
// ❌ Hard to scan: Complex ternary
const sortParam = query
? sort === "score"
? "score"
: `${sort}_desc`
: `${sort}_desc`;
```
## Rule
Make code scannable by extracting intent into variables and helper functions. If you need to think about what code does, extract it. The reader should understand the flow without diving into implementation details.
+1
View File
@@ -0,0 +1 @@
!apps/web/.env.example
+13 -12
View File
@@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our
community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
- Focusing on what is best not just for us as individuals, but for the overall
* Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or advances of
* The use of sexualized language or imagery, and sexual attention or advances of
any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email address,
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address,
without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
@@ -59,7 +59,8 @@ representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at our discord server.
reported to the community leaders responsible for enforcement at
[INSERT CONTACT METHOD].
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
@@ -86,4 +87,4 @@ version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
+2 -2
View File
@@ -98,11 +98,11 @@ If you're unsure whether your idea falls into the preview category, feel free to
```bash
# Database (matches docker-compose.yaml)
DATABASE_URL="postgresql://opencut:opencut@localhost:5432/opencut"
DATABASE_URL="postgresql://opencut:opencutthegoat@localhost:5432/opencut"
# Generate a secure secret for Better Auth
BETTER_AUTH_SECRET="your-generated-secret-here"
NEXT_PUBLIC_SITE_URL="http://localhost:3000"
NEXT_PUBLIC_BETTER_AUTH_URL="http://localhost:3000"
# Redis (matches docker-compose.yaml)
UPSTASH_REDIS_REST_URL="http://localhost:8079"
+3 -3
View File
@@ -1,6 +1,6 @@
name: Bug report
description: Create a report to help us improve
title: "[BUG] "
title: '[BUG] '
labels: bug
body:
- type: input
@@ -15,7 +15,7 @@ body:
id: Browser
attributes:
label: Browser
description: Please enter the browser on which you encountered the bug.
description: Please enter the browser on which you encountered the bug.
placeholder: e.g. Chrome 137, Firefox 137, Safari 17
validations:
required: true
@@ -67,4 +67,4 @@ body:
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
validations:
required: false
required: false
+2 -2
View File
@@ -1,6 +1,6 @@
name: Feature request
description: Suggest an idea for OpenCut
title: "[FEATURE] "
title: '[FEATURE] '
labels: enhancement
body:
- type: markdown
@@ -39,4 +39,4 @@ body:
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
validations:
required: false
required: false
+1 -1
View File
@@ -25,4 +25,4 @@ Please do not report security vulnerabilities through public GitHub issues.
- We will provide a detailed response within 5 business days
- We will keep you updated on our progress
Thank you for helping keep OpenCut secure!
Thank you for helping keep OpenCut secure!
+1 -5
View File
@@ -3,25 +3,21 @@
Thanks for using OpenCut! If you need help, here are your options:
## Documentation
- Check our [README](../README.md) for basic setup instructions
- Review the [Contributing Guidelines](CONTRIBUTING.md) for development setup
## Issues
- **Bug reports**: Use the bug report template
- **Feature requests**: Use the feature request template
- **Questions**: Use GitHub Discussions for general questions
## Community
- Join our discussions on GitHub
- Follow the [Code of Conduct](CODE_OF_CONDUCT.md)
## Response Times
- Issues are typically triaged within 2-3 business days
- Feature requests may take longer to evaluate
- Security issues are handled with priority
We appreciate your patience and contributions to making OpenCut better!
We appreciate your patience and contributions to making OpenCut better!
-344
View File
@@ -1,344 +0,0 @@
---
applyTo: "**/*.{ts,tsx,js,jsx}"
---
# Project Context
Ultracite enforces strict type safety, accessibility standards, and consistent code quality for JavaScript/TypeScript projects using Biome's lightning-fast formatter and linter.
## Key Principles
- Zero configuration required
- Subsecond performance
- Maximum type safety
- AI-friendly code generation
## Before Writing Code
1. Analyze existing patterns in the codebase
2. Consider edge cases and error scenarios
3. Follow the rules below strictly
4. Validate accessibility requirements
## Rules
### Accessibility (a11y)
- Don't use `accessKey` attribute on any HTML element.
- Don't set `aria-hidden="true"` on focusable elements.
- Don't add ARIA roles, states, and properties to elements that don't support them.
- Don't use distracting elements like `<marquee>` or `<blink>`.
- Only use the `scope` prop on `<th>` elements.
- Don't assign non-interactive ARIA roles to interactive HTML elements.
- Make sure label elements have text content and are associated with an input.
- Don't assign interactive ARIA roles to non-interactive HTML elements.
- Don't assign `tabIndex` to non-interactive HTML elements.
- Don't use positive integers for `tabIndex` property.
- Don't include "image", "picture", or "photo" in img alt prop.
- Don't use explicit role property that's the same as the implicit/default role.
- Make static elements with click handlers use a valid role attribute.
- Always include a `title` element for SVG elements.
- Give all elements requiring alt text meaningful information for screen readers.
- Make sure anchors have content that's accessible to screen readers.
- Assign `tabIndex` to non-interactive HTML elements with `aria-activedescendant`.
- Include all required ARIA attributes for elements with ARIA roles.
- Make sure ARIA properties are valid for the element's supported roles.
- Always include a `type` attribute for button elements.
- Make elements with interactive roles and handlers focusable.
- Give heading elements content that's accessible to screen readers (not hidden with `aria-hidden`).
- Always include a `lang` attribute on the html element.
- Always include a `title` attribute for iframe elements.
- Accompany `onClick` with at least one of: `onKeyUp`, `onKeyDown`, or `onKeyPress`.
- Accompany `onMouseOver`/`onMouseOut` with `onFocus`/`onBlur`.
- Include caption tracks for audio and video elements.
- Use semantic elements instead of role attributes in JSX.
- Make sure all anchors are valid and navigable.
- Ensure all ARIA properties (`aria-*`) are valid.
- Use valid, non-abstract ARIA roles for elements with ARIA roles.
- Use valid ARIA state and property values.
- Use valid values for the `autocomplete` attribute on input elements.
- Use correct ISO language/country codes for the `lang` attribute.
### Code Complexity and Quality
- Don't use consecutive spaces in regular expression literals.
- Don't use the `arguments` object.
- Don't use primitive type aliases or misleading types.
- Don't use the comma operator.
- Don't use empty type parameters in type aliases and interfaces.
- Don't write functions that exceed a given Cognitive Complexity score.
- Don't nest describe() blocks too deeply in test files.
- Don't use unnecessary boolean casts.
- Don't use unnecessary callbacks with flatMap.
- Use for...of statements instead of Array.forEach.
- Don't create classes that only have static members (like a static namespace).
- Don't use this and super in static contexts.
- Don't use unnecessary catch clauses.
- Don't use unnecessary constructors.
- Don't use unnecessary continue statements.
- Don't export empty modules that don't change anything.
- Don't use unnecessary escape sequences in regular expression literals.
- Don't use unnecessary fragments.
- Don't use unnecessary labels.
- Don't use unnecessary nested block statements.
- Don't rename imports, exports, and destructured assignments to the same name.
- Don't use unnecessary string or template literal concatenation.
- Don't use String.raw in template literals when there are no escape sequences.
- Don't use useless case statements in switch statements.
- Don't use ternary operators when simpler alternatives exist.
- Don't use useless `this` aliasing.
- Don't use any or unknown as type constraints.
- Don't initialize variables to undefined.
- Don't use the void operators (they're not familiar).
- Use arrow functions instead of function expressions.
- Use Date.now() to get milliseconds since the Unix Epoch.
- Use .flatMap() instead of map().flat() when possible.
- Use literal property access instead of computed property access.
- Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
- Use concise optional chaining instead of chained logical expressions.
- Use regular expression literals instead of the RegExp constructor when possible.
- Don't use number literal object member names that aren't base 10 or use underscore separators.
- Remove redundant terms from logical expressions.
- Use while loops instead of for loops when you don't need initializer and update expressions.
- Don't pass children as props.
- Don't reassign const variables.
- Don't use constant expressions in conditions.
- Don't use `Math.min` and `Math.max` to clamp values when the result is constant.
- Don't return a value from a constructor.
- Don't use empty character classes in regular expression literals.
- Don't use empty destructuring patterns.
- Don't call global object properties as functions.
- Don't declare functions and vars that are accessible outside their block.
- Make sure builtins are correctly instantiated.
- Don't use super() incorrectly inside classes. Also check that super() is called in classes that extend other constructors.
- Don't use variables and function parameters before they're declared.
- Don't use 8 and 9 escape sequences in string literals.
- Don't use literal numbers that lose precision.
### React and JSX Best Practices
- Don't use the return value of React.render.
- Make sure all dependencies are correctly specified in React hooks.
- Make sure all React hooks are called from the top level of component functions.
- Don't forget key props in iterators and collection literals.
- Don't destructure props inside JSX components in Solid projects.
- Don't define React components inside other components.
- Don't use event handlers on non-interactive elements.
- Don't assign to React component props.
- Don't use both `children` and `dangerouslySetInnerHTML` props on the same element.
- Don't use dangerous JSX props.
- Don't use Array index in keys.
- Don't insert comments as text nodes.
- Don't assign JSX properties multiple times.
- Don't add extra closing tags for components without children.
- Use `<>...</>` instead of `<Fragment>...</Fragment>`.
- Watch out for possible "wrong" semicolons inside JSX elements.
### Correctness and Safety
- Don't assign a value to itself.
- Don't return a value from a setter.
- Don't compare expressions that modify string case with non-compliant values.
- Don't use lexical declarations in switch clauses.
- Don't use variables that haven't been declared in the document.
- Don't write unreachable code.
- Make sure super() is called exactly once on every code path in a class constructor before this is accessed if the class has a superclass.
- Don't use control flow statements in finally blocks.
- Don't use optional chaining where undefined values aren't allowed.
- Don't have unused function parameters.
- Don't have unused imports.
- Don't have unused labels.
- Don't have unused private class members.
- Don't have unused variables.
- Make sure void (self-closing) elements don't have children.
- Don't return a value from a function with the return type 'void'
- Use isNaN() when checking for NaN.
- Make sure "for" loop update clauses move the counter in the right direction.
- Make sure typeof expressions are compared to valid values.
- Make sure generator functions contain yield.
- Don't use await inside loops.
- Don't use bitwise operators.
- Don't use expressions where the operation doesn't change the value.
- Make sure Promise-like statements are handled appropriately.
- Don't use **dirname and **filename in the global scope.
- Prevent import cycles.
- Don't use configured elements.
- Don't hardcode sensitive data like API keys and tokens.
- Don't let variable declarations shadow variables from outer scopes.
- Don't use the TypeScript directive @ts-ignore.
- Prevent duplicate polyfills from Polyfill.io.
- Don't use useless backreferences in regular expressions that always match empty strings.
- Don't use unnecessary escapes in string literals.
- Don't use useless undefined.
- Make sure getters and setters for the same property are next to each other in class and object definitions.
- Make sure object literals are declared consistently (defaults to explicit definitions).
- Use static Response methods instead of new Response() constructor when possible.
- Make sure switch-case statements are exhaustive.
- Make sure the `preconnect` attribute is used when using Google Fonts.
- Use `Array#{indexOf,lastIndexOf}()` instead of `Array#{findIndex,findLastIndex}()` when looking for the index of an item.
- Make sure iterable callbacks return consistent values.
- Use `with { type: "json" }` for JSON module imports.
- Use numeric separators in numeric literals.
- Use object spread instead of `Object.assign()` when constructing new objects.
- Always use the radix argument when using `parseInt()`.
- Make sure JSDoc comment lines start with a single asterisk, except for the first one.
- Include a description parameter for `Symbol()`.
- Don't use spread (`...`) syntax on accumulators.
- Don't use the `delete` operator.
- Don't access namespace imports dynamically.
- Don't use namespace imports.
- Declare regex literals at the top level.
- Don't use `target="_blank"` without `rel="noopener"`.
### TypeScript Best Practices
- Don't use TypeScript enums.
- Don't export imported variables.
- Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
- Don't use TypeScript namespaces.
- Don't use non-null assertions with the `!` postfix operator.
- Don't use parameter properties in class constructors.
- Don't use user-defined types.
- Use `as const` instead of literal types and type annotations.
- Use either `T[]` or `Array<T>` consistently.
- Initialize each enum member value explicitly.
- Use `export type` for types.
- Use `import type` for types.
- Make sure all enum members are literal values.
- Don't use TypeScript const enum.
- Don't declare empty interfaces.
- Don't let variables evolve into any type through reassignments.
- Don't use the any type.
- Don't misuse the non-null assertion operator (!) in TypeScript files.
- Don't use implicit any type on variable declarations.
- Don't merge interfaces and classes unsafely.
- Don't use overload signatures that aren't next to each other.
- Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
### Style and Consistency
- Don't use global `eval()`.
- Don't use callbacks in asynchronous tests and hooks.
- Don't use negation in `if` statements that have `else` clauses.
- Don't use nested ternary expressions.
- Don't reassign function parameters.
- This rule lets you specify global variable names you don't want to use in your application.
- Don't use specified modules when loaded by import or require.
- Don't use constants whose value is the upper-case version of their name.
- Use `String.slice()` instead of `String.substr()` and `String.substring()`.
- Don't use template literals if you don't need interpolation or special-character handling.
- Don't use `else` blocks when the `if` block breaks early.
- Don't use yoda expressions.
- Don't use Array constructors.
- Use `at()` instead of integer index access.
- Follow curly brace conventions.
- Use `else if` instead of nested `if` statements in `else` clauses.
- Use single `if` statements instead of nested `if` clauses.
- Use `new` for all builtins except `String`, `Number`, and `Boolean`.
- Use consistent accessibility modifiers on class properties and methods.
- Use `const` declarations for variables that are only assigned once.
- Put default function parameters and optional function parameters last.
- Include a `default` clause in switch statements.
- Use the `**` operator instead of `Math.pow`.
- Use `for-of` loops when you need the index to extract an item from the iterated array.
- Use `node:assert/strict` over `node:assert`.
- Use the `node:` protocol for Node.js builtin modules.
- Use Number properties instead of global ones.
- Use assignment operator shorthand where possible.
- Use function types instead of object types with call signatures.
- Use template literals over string concatenation.
- Use `new` when throwing an error.
- Don't throw non-Error values.
- Use `String.trimStart()` and `String.trimEnd()` over `String.trimLeft()` and `String.trimRight()`.
- Use standard constants instead of approximated literals.
- Don't assign values in expressions.
- Don't use async functions as Promise executors.
- Don't reassign exceptions in catch clauses.
- Don't reassign class members.
- Don't compare against -0.
- Don't use labeled statements that aren't loops.
- Don't use void type outside of generic or return types.
- Don't use console.
- Don't use control characters and escape sequences that match control characters in regular expression literals.
- Don't use debugger.
- Don't assign directly to document.cookie.
- Use `===` and `!==`.
- Don't use duplicate case labels.
- Don't use duplicate class members.
- Don't use duplicate conditions in if-else-if chains.
- Don't use two keys with the same name inside objects.
- Don't use duplicate function parameter names.
- Don't have duplicate hooks in describe blocks.
- Don't use empty block statements and static blocks.
- Don't let switch clauses fall through.
- Don't reassign function declarations.
- Don't allow assignments to native objects and read-only global variables.
- Use Number.isFinite instead of global isFinite.
- Use Number.isNaN instead of global isNaN.
- Don't assign to imported bindings.
- Don't use irregular whitespace characters.
- Don't use labels that share a name with a variable.
- Don't use characters made with multiple code points in character class syntax.
- Make sure to use new and constructor properly.
- Don't use shorthand assign when the variable appears on both sides.
- Don't use octal escape sequences in string literals.
- Don't use Object.prototype builtins directly.
- Don't redeclare variables, functions, classes, and types in the same scope.
- Don't have redundant "use strict".
- Don't compare things where both sides are exactly the same.
- Don't let identifiers shadow restricted names.
- Don't use sparse arrays (arrays with holes).
- Don't use template literal placeholder syntax in regular strings.
- Don't use the then property.
- Don't use unsafe negation.
- Don't use var.
- Don't use with statements in non-strict contexts.
- Make sure async functions actually use await.
- Make sure default clauses in switch statements come last.
- Make sure to pass a message value when creating a built-in error.
- Make sure get methods always return a value.
- Use a recommended display strategy with Google Fonts.
- Make sure for-in loops include an if statement.
- Use Array.isArray() instead of instanceof Array.
- Make sure to use the digits argument with Number#toFixed().
- Make sure to use the "use strict" directive in script files.
### Next.js Specific Rules
- Don't use `<img>` elements in Next.js projects.
- Don't use `<head>` elements in Next.js projects.
- Don't import next/document outside of pages/\_document.jsx in Next.js projects.
- Don't use the next/head module in pages/\_document.js on Next.js projects.
### Testing Best Practices
- Don't use export or module.exports in test files.
- Don't use focused tests.
- Make sure the assertion function, like expect, is placed inside an it() function call.
- Don't use disabled tests.
## Common Tasks
- `npx ultracite init` - Initialize Ultracite in your project
- `npx ultracite format` - Format and fix code automatically
- `npx ultracite lint` - Check for issues without fixing
## Example: Error Handling
```typescript
// ✅ Good: Comprehensive error handling
try {
const result = await fetchData();
return { success: true, data: result };
} catch (error) {
console.error("API call failed:", error);
return { success: false, error: error.message };
}
// ❌ Bad: Swallowing errors
try {
return await fetchData();
} catch (e) {
console.log(e);
}
```
+4 -5
View File
@@ -24,10 +24,9 @@ Please describe the tests that you ran to verify your changes. Provide instructi
- [ ] Test B
**Test Configuration**:
- Node version:
- Browser (if applicable):
- Operating System:
* Node version:
* Browser (if applicable):
* Operating System:
## Screenshots (if applicable)
@@ -47,4 +46,4 @@ Add screenshots to help explain your changes.
## Additional context
Add any other context about the pull request here.
Add any other context about the pull request here.
+2 -2
View File
@@ -22,9 +22,9 @@ jobs:
os: [ubuntu-latest, windows-latest, macos-latest]
env:
DATABASE_URL: "postgresql://opencut:opencut@localhost:5432/opencut"
DATABASE_URL: "postgresql://opencut:opencutthegoat@localhost:5432/opencut"
BETTER_AUTH_SECRET: "supersecret"
NEXT_PUBLIC_SITE_URL: "http://localhost:3000"
NEXT_PUBLIC_BETTER_AUTH_URL: "http://localhost:3000"
UPSTASH_REDIS_REST_URL: "https://your-upstash-redis-url"
UPSTASH_REDIS_REST_TOKEN: "your-upstash-redis-token"
+22 -4
View File
@@ -1,5 +1,22 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/apps/web/node_modules
# next.js
/apps/web/.next/
/apps/web/out/
# debug
/apps/web/npm-debug.log*
# env files (can opt-in for committing if needed)
/apps/web/.env*
!/apps/web/.env.example
# typescript
/apps/web/next-env.d.ts
# asdf version management
.tool-versions
@@ -7,11 +24,12 @@ node_modules
.cursorignore
.turbo
.env*
!*.env.example
*.env
# cursor
.cursor/
bun.lockb
# Twiggy
.cursor/rules/file-structure.mdc
# mac
.DS_Store
-20
View File
@@ -1,20 +0,0 @@
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[javascript][typescript][javascriptreact][typescriptreact][json][jsonc][css][graphql]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"typescript.tsdk": "node_modules/typescript/lib",
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"editor.codeActionsOnSave": {
"source.fixAll.biome": "explicit",
"source.organizeImports.biome": "explicit"
},
"emmet.showExpandedAbbreviation": "never",
"[typescriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
}
}
-121
View File
@@ -1,121 +0,0 @@
# AGENTS.md
## Overview
Privacy-first video editor, with a focus on simplicity and ease of use.
## Lib vs Utils
- `lib/` - domain logic (specific to this app)
- `utils/` - small helper utils (generic, could be copy-pasted into any other app)
## Core Editor System
The editor uses a **singleton EditorCore** that manages all editor state through specialized managers.
### Architecture
```
EditorCore (singleton)
├── playback: PlaybackManager
├── timeline: TimelineManager
├── scene: SceneManager
├── project: ProjectManager
├── media: MediaManager
└── renderer: RendererManager
```
### When to Use What
#### In React Components
**Always use the `useEditor()` hook:**
```typescript
import { useEditor } from '@/hooks/use-editor';
function MyComponent() {
const editor = useEditor();
const tracks = editor.timeline.getTracks();
// Call methods
editor.timeline.addTrack({ type: 'media' });
// Display data (auto re-renders on changes)
return <div>{tracks.length} tracks</div>;
}
```
The hook:
- Returns the singleton instance
- Subscribes to all manager changes
- Automatically re-renders when state changes
#### Outside React Components
**Use `EditorCore.getInstance()` directly:**
```typescript
// In utilities, event handlers, or non-React code
import { EditorCore } from "@/core";
const editor = EditorCore.getInstance();
await editor.export({ format: "mp4", quality: "high" });
```
## Actions System
Actions are the trigger layer for user-initiated operations. The single source of truth is `@/lib/actions/definitions.ts`.
**To add a new action:**
1. Add it to `ACTIONS` in `@/lib/actions/definitions.ts`:
```typescript
export const ACTIONS = {
"my-action": {
description: "What the action does",
category: "editing",
defaultShortcuts: ["ctrl+m"],
},
// ...
};
```
2. Add handler in `@/hooks/use-editor-actions.ts`:
```typescript
useActionHandler(
"my-action",
() => {
// implementation
},
undefined,
);
```
**In components, use `invokeAction()` for user-triggered operations:**
```typescript
import { invokeAction } from '@/lib/actions';
// Good - uses action system
const handleSplit = () => invokeAction("split-selected");
// Avoid - bypasses UX layer (toasts, validation feedback)
const handleSplit = () => editor.timeline.splitElements({ ... });
```
Direct `editor.xxx()` calls are for internal use (commands, tests, complex multi-step operations).
## Commands System
Commands handle undo/redo. They live in `@/lib/commands/` organized by domain (timeline, media, scene).
Each command extends `Command` from `@/lib/commands/base-command` and implements:
- `execute()` - saves current state, then does the mutation
- `undo()` - restores the saved state
Actions and commands work together: actions are "what triggered this", commands are "how to do it (and undo it)".
+6 -6
View File
@@ -1,7 +1,7 @@
Copyright 2025 OpenCut
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
Copyright 2025 OpenCut
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+4 -10
View File
@@ -1,10 +1,10 @@
<table width="100%">
<tr>
<td align="left" width="120">
<img src="apps/web/public/logos/opencut/1k/logo.png" alt="OpenCut Logo" width="100" />
<img src="apps/web/public/logo.png" alt="OpenCut Logo" width="100" />
</td>
<td align="right">
<h1>OpenCut</span></h1>
<h1>OpenCut <span style="font-size: 0.7em; font-weight: normal;">(prev AppCut)</span></h1>
<h3 style="margin-top: -10px;">A free, open-source video editor for web, desktop, and mobile.</h3>
</td>
</tr>
@@ -104,7 +104,7 @@ Before you begin, ensure you have the following installed on your system:
```bash
# Database (matches docker-compose.yaml)
DATABASE_URL="postgresql://opencut:opencut@localhost:5432/opencut"
DATABASE_URL="postgresql://opencut:opencutthegoat@localhost:5432/opencut"
# Generate a secure secret for Better Auth
BETTER_AUTH_SECRET="your-generated-secret-here"
@@ -160,18 +160,12 @@ See our [Contributing Guide](.github/CONTRIBUTING.md) for detailed setup instruc
## Sponsors
Thanks to [Vercel](https://vercel.com?utm_source=github-opencut&utm_campaign=oss) and [fal.ai](https://fal.ai?utm_source=github-opencut&utm_campaign=oss) for their support of open-source software.
Thanks to [Vercel](https://vercel.com?utm_source=github-opencut&utm_campaign=oss) for their support of open-source software.
<a href="https://vercel.com/oss">
<img alt="Vercel OSS Program" src="https://vercel.com/oss/program-badge.svg" />
</a>
<a href="https://fal.ai">
<img alt="Powered by fal.ai" src="https://img.shields.io/badge/Powered%20by-fal.ai-000000?style=flat&logo=data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEyIDJMMTMuMDkgOC4yNkwyMCAxMEwxMy4wOSAxNS43NEwxMiAyMkwxMC45MSAxNS43NEw0IDEwTDEwLjkxIDguMjZMMTIgMloiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=" />
</a>
---
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FOpenCut-app%2FOpenCut&project-name=opencut&repository-name=opencut)
## License
+13 -22
View File
@@ -1,28 +1,19 @@
# Environment variables example
# Environment Variables Example
# Copy this file to .env.local and update the values as needed
# Node
DATABASE_URL="postgresql://opencut:opencutthegoat@localhost:5432/opencut"
# Better Auth
NEXT_PUBLIC_BETTER_AUTH_URL=http://localhost:3000
BETTER_AUTH_SECRET=your-secret-key-here
# Development Environment
NODE_ENV=development
# Public
NEXT_PUBLIC_SITE_URL=http://localhost:3000
NEXT_PUBLIC_MARBLE_API_URL=https://api.marblecms.com
# Server
DATABASE_URL="postgresql://opencut:opencut@localhost:5432/opencut"
BETTER_AUTH_SECRET=your_better_auth_secret
# Redis
UPSTASH_REDIS_REST_URL=http://localhost:8079
UPSTASH_REDIS_REST_TOKEN=example_token_here
UPSTASH_REDIS_REST_TOKEN=example_token
MARBLE_WORKSPACE_KEY=your_workspace_key_here
FREESOUND_CLIENT_ID=your_client_id_here
FREESOUND_API_KEY=your_api_key_here
CLOUDFLARE_ACCOUNT_ID=your_account_id_here
R2_ACCESS_KEY_ID=your_access_key_here
R2_SECRET_ACCESS_KEY=your_secret_key_here
R2_BUCKET_NAME=opencut-transcription # whatever you named your r2 bucket
MODAL_TRANSCRIPTION_URL=your_modal_url_here
# Marble Blog
MARBLE_WORKSPACE_KEY=cm6ytuq9x0000i803v0isidst # example organization key
NEXT_PUBLIC_MARBLE_API_URL=https://api.marblecms.com
+1 -7
View File
@@ -1,8 +1,2 @@
# Turborepo
.turbo
# Env vars
.env*
!.env.example
.next/
.turbo
+57
View File
@@ -0,0 +1,57 @@
FROM oven/bun:alpine AS base
# Install dependencies and build the application
FROM base AS builder
WORKDIR /app
COPY package.json package.json
COPY bun.lock bun.lock
COPY turbo.json turbo.json
COPY apps/web/package.json apps/web/package.json
COPY packages/db/package.json packages/db/package.json
COPY packages/auth/package.json packages/auth/package.json
RUN bun install
COPY apps/web/ apps/web/
COPY packages/db/ packages/db/
COPY packages/auth/ packages/auth/
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
# Set build-time environment variables for validation
ENV DATABASE_URL="postgresql://opencut:opencutthegoat@localhost:5432/opencut"
ENV BETTER_AUTH_SECRET="build-time-secret"
ENV UPSTASH_REDIS_REST_URL="http://localhost:8079"
ENV UPSTASH_REDIS_REST_TOKEN="example_token"
ENV NEXT_PUBLIC_BETTER_AUTH_URL="http://localhost:3000"
WORKDIR /app/apps/web
RUN bun run build
# Production image
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static
RUN chown nextjs:nodejs apps
USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
CMD ["bun", "apps/web/server.js"]
+2 -2
View File
@@ -4,7 +4,7 @@
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"config": "tailwind.config.ts",
"css": "src/app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
@@ -18,4 +18,4 @@
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}
}
+19 -23
View File
@@ -1,23 +1,19 @@
import type { Config } from "drizzle-kit";
import * as dotenv from "dotenv";
import { webEnv } from "@opencut/env/web";
// Load the right env file based on environment
if (webEnv.NODE_ENV === "production") {
dotenv.config({ path: ".env.production" });
} else {
dotenv.config({ path: ".env.local" });
}
export default {
schema: "./src/schema.ts",
dialect: "postgresql",
migrations: {
table: "drizzle_migrations",
},
dbCredentials: {
url: webEnv.DATABASE_URL,
},
out: "./migrations",
strict: webEnv.NODE_ENV === "production",
} satisfies Config;
import type { Config } from "drizzle-kit";
import * as dotenv from "dotenv";
// Load the right env file based on environment
if (process.env.NODE_ENV === "production") {
dotenv.config({ path: ".env.production" });
} else {
dotenv.config({ path: ".env.local" });
}
export default {
schema: "../../packages/db/src/schema.ts",
dialect: "postgresql",
dbCredentials: {
url: process.env.DATABASE_URL!,
},
out: "./migrations",
strict: process.env.NODE_ENV === "production",
} satisfies Config;
@@ -0,0 +1,54 @@
CREATE TABLE "accounts" (
"id" text PRIMARY KEY NOT NULL,
"account_id" text NOT NULL,
"provider_id" text NOT NULL,
"user_id" text NOT NULL,
"access_token" text,
"refresh_token" text,
"id_token" text,
"access_token_expires_at" timestamp,
"refresh_token_expires_at" timestamp,
"scope" text,
"password" text,
"created_at" timestamp NOT NULL,
"updated_at" timestamp NOT NULL
);
--> statement-breakpoint
ALTER TABLE "accounts" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
CREATE TABLE "sessions" (
"id" text PRIMARY KEY NOT NULL,
"expires_at" timestamp NOT NULL,
"token" text NOT NULL,
"created_at" timestamp NOT NULL,
"updated_at" timestamp NOT NULL,
"ip_address" text,
"user_agent" text,
"user_id" text NOT NULL,
CONSTRAINT "sessions_token_unique" UNIQUE("token")
);
--> statement-breakpoint
ALTER TABLE "sessions" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
CREATE TABLE "users" (
"id" text PRIMARY KEY NOT NULL,
"name" text NOT NULL,
"email" text NOT NULL,
"email_verified" boolean NOT NULL,
"image" text,
"created_at" timestamp NOT NULL,
"updated_at" timestamp NOT NULL,
CONSTRAINT "users_email_unique" UNIQUE("email")
);
--> statement-breakpoint
ALTER TABLE "users" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
CREATE TABLE "verifications" (
"id" text PRIMARY KEY NOT NULL,
"identifier" text NOT NULL,
"value" text NOT NULL,
"expires_at" timestamp NOT NULL,
"created_at" timestamp,
"updated_at" timestamp
);
--> statement-breakpoint
ALTER TABLE "verifications" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
ALTER TABLE "accounts" ADD CONSTRAINT "accounts_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
@@ -0,0 +1,8 @@
CREATE TABLE "waitlist" (
"id" text PRIMARY KEY NOT NULL,
"email" text NOT NULL,
"created_at" timestamp NOT NULL,
CONSTRAINT "waitlist_email_unique" UNIQUE("email")
);
--> statement-breakpoint
ALTER TABLE "waitlist" ENABLE ROW LEVEL SECURITY;
@@ -0,0 +1 @@
ALTER TABLE "users" ALTER COLUMN "email_verified" SET DEFAULT false;
+20 -45
View File
@@ -1,5 +1,5 @@
{
"id": "33a6742f-89da-4ac5-958f-421aa1cf9bd6",
"id": "4440fb90-cf0e-4cb2-afad-08250ce3dc1e",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
@@ -93,8 +93,12 @@
"name": "accounts_user_id_users_id_fk",
"tableFrom": "accounts",
"tableTo": "users",
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
@@ -164,8 +168,12 @@
"name": "sessions_user_id_users_id_fk",
"tableFrom": "sessions",
"tableTo": "users",
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
@@ -175,7 +183,9 @@
"sessions_token_unique": {
"name": "sessions_token_unique",
"nullsNotDistinct": false,
"columns": ["token"]
"columns": [
"token"
]
}
},
"policies": {},
@@ -236,7 +246,9 @@
"users_email_unique": {
"name": "users_email_unique",
"nullsNotDistinct": false,
"columns": ["email"]
"columns": [
"email"
]
}
},
"policies": {},
@@ -291,43 +303,6 @@
"policies": {},
"checkConstraints": {},
"isRLSEnabled": true
},
"public.waitlist": {
"name": "waitlist",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"waitlist_email_unique": {
"name": "waitlist_email_unique",
"nullsNotDistinct": false,
"columns": ["email"]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": true
}
},
"enums": {},
@@ -341,4 +316,4 @@
"schemas": {},
"tables": {}
}
}
}
+358
View File
@@ -0,0 +1,358 @@
{
"id": "b7d920ca-6dd0-430f-8ee6-1d38fdf3e80f",
"prevId": "4440fb90-cf0e-4cb2-afad-08250ce3dc1e",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.accounts": {
"name": "accounts",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"account_id": {
"name": "account_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"provider_id": {
"name": "provider_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"access_token": {
"name": "access_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"refresh_token": {
"name": "refresh_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"id_token": {
"name": "id_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"access_token_expires_at": {
"name": "access_token_expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"refresh_token_expires_at": {
"name": "refresh_token_expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"scope": {
"name": "scope",
"type": "text",
"primaryKey": false,
"notNull": false
},
"password": {
"name": "password",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"accounts_user_id_users_id_fk": {
"name": "accounts_user_id_users_id_fk",
"tableFrom": "accounts",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": true
},
"public.sessions": {
"name": "sessions",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"expires_at": {
"name": "expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"token": {
"name": "token",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"ip_address": {
"name": "ip_address",
"type": "text",
"primaryKey": false,
"notNull": false
},
"user_agent": {
"name": "user_agent",
"type": "text",
"primaryKey": false,
"notNull": false
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"sessions_user_id_users_id_fk": {
"name": "sessions_user_id_users_id_fk",
"tableFrom": "sessions",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"sessions_token_unique": {
"name": "sessions_token_unique",
"nullsNotDistinct": false,
"columns": [
"token"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": true
},
"public.users": {
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"email_verified": {
"name": "email_verified",
"type": "boolean",
"primaryKey": false,
"notNull": true
},
"image": {
"name": "image",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"users_email_unique": {
"name": "users_email_unique",
"nullsNotDistinct": false,
"columns": [
"email"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": true
},
"public.verifications": {
"name": "verifications",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"identifier": {
"name": "identifier",
"type": "text",
"primaryKey": false,
"notNull": true
},
"value": {
"name": "value",
"type": "text",
"primaryKey": false,
"notNull": true
},
"expires_at": {
"name": "expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": true
},
"public.waitlist": {
"name": "waitlist",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"waitlist_email_unique": {
"name": "waitlist_email_unique",
"nullsNotDistinct": false,
"columns": [
"email"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": true
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}
+359
View File
@@ -0,0 +1,359 @@
{
"id": "1b9148ed-497b-4e53-8cf6-f556c8fe7f7f",
"prevId": "b7d920ca-6dd0-430f-8ee6-1d38fdf3e80f",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.accounts": {
"name": "accounts",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"account_id": {
"name": "account_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"provider_id": {
"name": "provider_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"access_token": {
"name": "access_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"refresh_token": {
"name": "refresh_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"id_token": {
"name": "id_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"access_token_expires_at": {
"name": "access_token_expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"refresh_token_expires_at": {
"name": "refresh_token_expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"scope": {
"name": "scope",
"type": "text",
"primaryKey": false,
"notNull": false
},
"password": {
"name": "password",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"accounts_user_id_users_id_fk": {
"name": "accounts_user_id_users_id_fk",
"tableFrom": "accounts",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": true
},
"public.sessions": {
"name": "sessions",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"expires_at": {
"name": "expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"token": {
"name": "token",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"ip_address": {
"name": "ip_address",
"type": "text",
"primaryKey": false,
"notNull": false
},
"user_agent": {
"name": "user_agent",
"type": "text",
"primaryKey": false,
"notNull": false
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"sessions_user_id_users_id_fk": {
"name": "sessions_user_id_users_id_fk",
"tableFrom": "sessions",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"sessions_token_unique": {
"name": "sessions_token_unique",
"nullsNotDistinct": false,
"columns": [
"token"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": true
},
"public.users": {
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"email_verified": {
"name": "email_verified",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"image": {
"name": "image",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"users_email_unique": {
"name": "users_email_unique",
"nullsNotDistinct": false,
"columns": [
"email"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": true
},
"public.verifications": {
"name": "verifications",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"identifier": {
"name": "identifier",
"type": "text",
"primaryKey": false,
"notNull": true
},
"value": {
"name": "value",
"type": "text",
"primaryKey": false,
"notNull": true
},
"expires_at": {
"name": "expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": true
},
"public.waitlist": {
"name": "waitlist",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"waitlist_email_unique": {
"name": "waitlist_email_unique",
"nullsNotDistinct": false,
"columns": [
"email"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": true
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}
+17 -3
View File
@@ -5,9 +5,23 @@
{
"idx": 0,
"version": "7",
"when": 1750753385927,
"tag": "0000_brainy_saracen",
"when": 1750581188229,
"tag": "0000_hot_the_fallen",
"breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1750689835736,
"tag": "0001_tricky_jackpot",
"breakpoints": true
},
{
"idx": 2,
"version": "7",
"when": 1752569400809,
"tag": "0002_cuddly_pretty_boy",
"breakpoints": true
}
]
}
}
-6
View File
@@ -1,6 +0,0 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/dev/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
-12
View File
@@ -30,18 +30,6 @@ const nextConfig: NextConfig = {
protocol: "https",
hostname: "avatars.githubusercontent.com",
},
{
protocol: "https",
hostname: "api.iconify.design",
},
{
protocol: "https",
hostname: "api.simplesvg.com",
},
{
protocol: "https",
hostname: "api.unisvg.com",
},
],
},
};
+16 -31
View File
@@ -1,15 +1,13 @@
{
"name": "@opencut/web",
"name": "opencut",
"version": "0.1.0",
"private": true,
"packageManager": "bun@1.2.18",
"scripts": {
"dev": "next dev --turbopack",
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "biome check src/",
"lint:fix": "biome check src/ --write",
"format": "biome format src/ --write",
"lint": "next lint",
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:push:local": "cross-env NODE_ENV=development drizzle-kit push",
@@ -21,39 +19,30 @@
"@ffmpeg/util": "^0.12.2",
"@hello-pangea/dnd": "^18.0.1",
"@hookform/resolvers": "^3.9.1",
"@hugeicons/core-free-icons": "^3.1.1",
"@hugeicons/react": "^1.1.4",
"@huggingface/transformers": "^3.8.1",
"@opencut/env": "workspace:*",
"@opencut/ui": "workspace:*",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.8",
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-tooltip": "^1.2.8",
"@upstash/ratelimit": "^2.0.6",
"@upstash/redis": "^1.35.4",
"@opencut/auth": "workspace:*",
"@opencut/db": "workspace:*",
"@radix-ui/react-separator": "^1.1.7",
"@t3-oss/env-core": "^0.13.8",
"@t3-oss/env-nextjs": "^0.13.8",
"@upstash/ratelimit": "^2.0.5",
"@upstash/redis": "^1.35.0",
"@vercel/analytics": "^1.4.1",
"aws4fetch": "^1.0.20",
"better-auth": "^1.2.7",
"botid": "^1.4.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"dayjs": "^1.11.13",
"dotenv": "^16.5.0",
"drizzle-orm": "^0.44.2",
"embla-carousel-react": "^8.5.1",
"eventemitter3": "^5.0.1",
"feed": "^5.1.0",
"framer-motion": "^11.13.1",
"input-otp": "^1.4.1",
"lucide-react": "^0.562.0",
"mediabunny": "^1.29.1",
"lucide-react": "^0.468.0",
"motion": "^12.18.1",
"nanoid": "^5.1.5",
"next": "16.1.3",
"next": "^15.4.1",
"next-themes": "^0.4.4",
"pg": "^8.16.2",
"postgres": "^3.4.5",
"radix-ui": "^1.4.2",
"react": "^18.2.0",
"react-day-picker": "^8.10.1",
@@ -73,25 +62,21 @@
"tailwind-merge": "^2.5.5",
"tailwindcss-animate": "^1.0.7",
"unified": "^11.0.5",
"use-deep-compare-effect": "^1.8.1",
"vaul": "^1.1.1",
"wavesurfer.js": "^7.9.8",
"zod": "^3.25.67",
"zustand": "^5.0.2"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.11",
"@tailwindcss/typography": "^0.5.16",
"@types/bun": "latest",
"@types/node": "^24.2.1",
"@types/pg": "^8.15.4",
"@types/react": "^18.2.48",
"@types/react-dom": "^18.2.18",
"cross-env": "^7.0.3",
"drizzle-kit": "^0.31.4",
"dotenv": "^16.5.0",
"postcss": "^8",
"tailwindcss": "^4.1.11",
"prettier": "^3.4.2",
"tailwindcss": "^3.4.1",
"tsx": "^4.7.1",
"typescript": "^5.8.3"
}
+1 -1
View File
@@ -1,7 +1,7 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
"@tailwindcss/postcss": {},
tailwindcss: {},
},
};
File diff suppressed because one or more lines are too long
+10
View File
@@ -0,0 +1,10 @@
<svg width="459" height="77" viewBox="0 0 459 77" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="1.15625" width="458" height="75" rx="15.5" fill="#101010"/>
<rect x="0.5" y="1.15625" width="458" height="75" rx="15.5" stroke="#FFCC00"/>
<rect x="0.5" y="1.15625" width="31" height="75" rx="15.5" fill="#2A2A2A"/>
<rect x="0.5" y="1.15625" width="31" height="75" rx="15.5" stroke="#FFCC00"/>
<rect x="13" y="20.6562" width="6" height="36" rx="3" fill="#FFCC00"/>
<rect x="427.5" y="1.15625" width="31" height="75" rx="15.5" fill="#2A2A2A"/>
<rect x="427.5" y="1.15625" width="31" height="75" rx="15.5" stroke="#FFCC00"/>
<rect x="440" y="20.6562" width="6" height="36" rx="3" fill="#FFCC00"/>
</svg>

After

Width:  |  Height:  |  Size: 716 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 467 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 741 B

After

Width:  |  Height:  |  Size: 564 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 768 B

After

Width:  |  Height:  |  Size: 222 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 802 B

After

Width:  |  Height:  |  Size: 253 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 826 B

After

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 906 B

After

Width:  |  Height:  |  Size: 334 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 985 B

After

Width:  |  Height:  |  Size: 399 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 998 B

After

Width:  |  Height:  |  Size: 419 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 467 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 546 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 724 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 809 B

After

Width:  |  Height:  |  Size: 257 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 843 B

After

Width:  |  Height:  |  Size: 287 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 826 B

After

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 820 B

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 670 B

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 747 B

After

Width:  |  Height:  |  Size: 206 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 906 B

After

Width:  |  Height:  |  Size: 334 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 467 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 539 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 814 B

After

Width:  |  Height:  |  Size: 259 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1021 B

Before

Width:  |  Height:  |  Size: 362 B

After

Width:  |  Height:  |  Size: 362 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

-3
View File
@@ -1,3 +0,0 @@
<svg width="512" height="513" viewBox="0 0 512 513" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M330.144 0.740479C339.014 0.740479 346.122 7.95256 346.971 16.7816C354.534 95.534 417.207 158.206 495.959 165.769C504.788 166.618 512 173.726 512 182.596V330.886C512 339.755 504.788 346.863 495.959 347.712C417.207 355.275 354.534 417.947 346.971 496.7C346.122 505.529 339.014 512.74 330.144 512.74H181.856C172.986 512.74 165.878 505.529 165.029 496.7C157.467 417.947 94.7934 355.275 16.041 347.712C7.21212 346.863 0 339.755 0 330.886V182.596C0 173.726 7.21216 166.618 16.0411 165.769C94.7935 158.206 157.467 95.534 165.029 16.7816C165.878 7.95256 172.986 0.740479 181.856 0.740479H330.144ZM102.8 256.307C102.8 341.495 171.781 410.554 256.874 410.554C341.966 410.554 410.948 341.495 410.948 256.307C410.948 171.119 341.966 102.06 256.874 102.06C171.781 102.06 102.8 171.119 102.8 256.307Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 960 B

-3
View File
@@ -1,3 +0,0 @@
<svg width="588" height="512" viewBox="0 0 588 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M294 0L588 512H0L294 0Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 156 B

+43 -43
View File
@@ -1,44 +1,44 @@
{
"name": "OpenCut",
"description": "A simple but powerful video editor that gets the job done. In your browser.",
"display": "standalone",
"start_url": "/",
"icons": [
{
"src": "/icons/android-icon-36x36.png",
"sizes": "36x36",
"type": "image\/png",
"density": "0.75"
},
{
"src": "/icons/android-icon-48x48.png",
"sizes": "48x48",
"type": "image\/png",
"density": "1.0"
},
{
"src": "/icons/android-icon-72x72.png",
"sizes": "72x72",
"type": "image\/png",
"density": "1.5"
},
{
"src": "/icons/android-icon-96x96.png",
"sizes": "96x96",
"type": "image\/png",
"density": "2.0"
},
{
"src": "/icons/android-icon-144x144.png",
"sizes": "144x144",
"type": "image\/png",
"density": "3.0"
},
{
"src": "/icons/android-icon-192x192.png",
"sizes": "192x192",
"type": "image\/png",
"density": "4.0"
}
]
}
"name": "OpenCut",
"description": "A simple but powerful video editor that gets the job done. In your browser.",
"display": "standalone",
"start_url": "/",
"icons": [
{
"src": "/icons/android-icon-36x36.png",
"sizes": "36x36",
"type": "image\/png",
"density": "0.75"
},
{
"src": "/icons/android-icon-48x48.png",
"sizes": "48x48",
"type": "image\/png",
"density": "1.0"
},
{
"src": "/icons/android-icon-72x72.png",
"sizes": "72x72",
"type": "image\/png",
"density": "1.5"
},
{
"src": "/icons/android-icon-96x96.png",
"sizes": "96x96",
"type": "image\/png",
"density": "2.0"
},
{
"src": "/icons/android-icon-144x144.png",
"sizes": "144x144",
"type": "image\/png",
"density": "3.0"
},
{
"src": "/icons/android-icon-192x192.png",
"sizes": "192x192",
"type": "image\/png",
"density": "4.0"
}
]
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

+146
View File
@@ -0,0 +1,146 @@
"use client";
import { useRouter } from "next/navigation";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { memo, Suspense } from "react";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator";
import Link from "next/link";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { ArrowLeft, Loader2 } from "lucide-react";
import { GoogleIcon } from "@/components/icons";
import { useLogin } from "@/hooks/auth/useLogin";
const LoginPage = () => {
const router = useRouter();
const {
email,
setEmail,
password,
setPassword,
error,
isAnyLoading,
isEmailLoading,
isGoogleLoading,
handleLogin,
handleGoogleLogin,
} = useLogin();
return (
<div className="flex h-screen items-center justify-center relative">
<Button
variant="text"
onClick={() => router.back()}
className="absolute top-6 left-6"
>
<ArrowLeft className="h-5 w-5" /> Back
</Button>
<Card className="w-[400px] shadow-lg border-0">
<CardHeader className="text-center pb-4">
<CardTitle className="text-2xl font-semibold">Welcome back</CardTitle>
<CardDescription className="text-base">
Sign in to your account to continue
</CardDescription>
</CardHeader>
<CardContent className="pt-0">
<Suspense
fallback={
<div className="text-center">
<Loader2 className="animate-spin" />
</div>
}
>
<div className="flex flex-col space-y-6">
{error && (
<Alert variant="destructive">
<AlertTitle>Error</AlertTitle>
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
<Button
onClick={handleGoogleLogin}
variant="outline"
size="lg"
disabled={isAnyLoading}
>
{isGoogleLoading ? (
<Loader2 className="animate-spin" />
) : (
<GoogleIcon />
)}{" "}
Continue with Google
</Button>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<Separator className="w-full" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background px-2 text-muted-foreground">
Or continue with
</span>
</div>
</div>
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="m@example.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
disabled={isAnyLoading}
className="h-11"
/>
</div>
<div className="space-y-2">
<Label htmlFor="password">Password</Label>
<Input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={isAnyLoading}
className="h-11"
/>
</div>
<Button
onClick={handleLogin}
disabled={isAnyLoading || !email || !password}
className="w-full h-11"
size="lg"
>
{isEmailLoading ? (
<Loader2 className="animate-spin" />
) : (
"Sign in"
)}
</Button>
</div>
</div>
<div className="mt-6 text-center text-sm">
Don't have an account?{" "}
<Link
href="/signup"
className="font-medium text-primary underline-offset-4 hover:underline"
>
Sign up
</Link>
</div>
</Suspense>
</CardContent>
</Card>
</div>
);
};
export default memo(LoginPage);
+162
View File
@@ -0,0 +1,162 @@
"use client";
import { useRouter } from "next/navigation";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { memo, Suspense } from "react";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator";
import Link from "next/link";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { ArrowLeft, Loader2 } from "lucide-react";
import { GoogleIcon } from "@/components/icons";
import { useSignUp } from "@/hooks/auth/useSignUp";
const SignUpPage = () => {
const router = useRouter();
const {
name,
setName,
email,
setEmail,
password,
setPassword,
error,
isAnyLoading,
isEmailLoading,
isGoogleLoading,
handleSignUp,
handleGoogleSignUp,
} = useSignUp();
return (
<div className="flex h-screen items-center justify-center relative">
<Button
variant="text"
onClick={() => router.back()}
className="absolute top-6 left-6"
>
<ArrowLeft className="h-5 w-5" /> Back
</Button>
<Card className="w-[400px] shadow-lg border-0">
<CardHeader className="text-center pb-4">
<CardTitle className="text-2xl font-semibold">
Create your account
</CardTitle>
<CardDescription className="text-base">
Get started with your free account today
</CardDescription>
</CardHeader>
<CardContent className="pt-0">
<Suspense
fallback={
<div className="text-center">
<Loader2 className="animate-spin" />
</div>
}
>
<div className="flex flex-col space-y-6">
{error && (
<Alert variant="destructive">
<AlertTitle>Error</AlertTitle>
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
<Button
onClick={handleGoogleSignUp}
variant="outline"
size="lg"
disabled={isAnyLoading}
>
{isGoogleLoading ? (
<Loader2 className="animate-spin" />
) : (
<GoogleIcon />
)}{" "}
Continue with Google
</Button>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<Separator className="w-full" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background px-2 text-muted-foreground">
Or continue with
</span>
</div>
</div>
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="name">Full Name</Label>
<Input
id="name"
type="text"
placeholder="John Doe"
value={name}
onChange={(e) => setName(e.target.value)}
disabled={isAnyLoading}
className="h-11"
/>
</div>
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="m@example.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
disabled={isAnyLoading}
className="h-11"
/>
</div>
<div className="space-y-2">
<Label htmlFor="password">Password</Label>
<Input
id="password"
type="password"
placeholder="Create a strong password"
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={isAnyLoading}
className="h-11"
/>
</div>
<Button
onClick={handleSignUp}
disabled={isAnyLoading || !name || !email || !password}
className="w-full h-11"
size="lg"
>
{isEmailLoading ? (
<Loader2 className="animate-spin" />
) : (
"Create account"
)}
</Button>
</div>
</div>
<div className="mt-6 text-center text-sm">
Already have an account?{" "}
<Link
href="/login"
className="font-medium text-primary underline-offset-4 hover:underline"
>
Sign in
</Link>
</div>
</Suspense>
</CardContent>
</Card>
</div>
);
};
export default memo(SignUpPage);
+4 -4
View File
@@ -1,4 +1,4 @@
import { auth } from "@/lib/auth/server";
import { toNextJsHandler } from "better-auth/next-js";
export const { POST, GET } = toNextJsHandler(auth);
import { auth } from "@opencut/auth";
import { toNextJsHandler } from "better-auth/next-js";
export const { POST, GET } = toNextJsHandler(auth);
+4 -2
View File
@@ -1,3 +1,5 @@
export async function GET() {
return new Response("OK", { status: 200 });
import { NextRequest } from "next/server";
export async function GET(request: NextRequest) {
return new Response("OK", { status: 200 });
}
-280
View File
@@ -1,280 +0,0 @@
import { webEnv } from "@opencut/env/web";
import { type NextRequest, NextResponse } from "next/server";
import { z } from "zod";
import { checkRateLimit } from "@/lib/rate-limit";
const searchParamsSchema = z.object({
q: z.string().max(500, "Query too long").optional(),
type: z.enum(["songs", "effects"]).optional(),
page: z.coerce.number().int().min(1).max(1000).default(1),
page_size: z.coerce.number().int().min(1).max(150).default(20),
sort: z
.enum(["downloads", "rating", "created", "score"])
.default("downloads"),
min_rating: z.coerce.number().min(0).max(5).default(3),
commercial_only: z.coerce.boolean().default(true),
});
const freesoundResultSchema = z.object({
id: z.number(),
name: z.string(),
description: z.string(),
url: z.string().url(),
previews: z
.object({
"preview-hq-mp3": z.string().url(),
"preview-lq-mp3": z.string().url(),
"preview-hq-ogg": z.string().url(),
"preview-lq-ogg": z.string().url(),
})
.optional(),
download: z.string().url().optional(),
duration: z.number(),
filesize: z.number(),
type: z.string(),
channels: z.number(),
bitrate: z.number(),
bitdepth: z.number(),
samplerate: z.number(),
username: z.string(),
tags: z.array(z.string()),
license: z.string(),
created: z.string(),
num_downloads: z.number().optional(),
avg_rating: z.number().optional(),
num_ratings: z.number().optional(),
});
const freesoundResponseSchema = z.object({
count: z.number(),
next: z.string().url().nullable(),
previous: z.string().url().nullable(),
results: z.array(freesoundResultSchema),
});
const transformedResultSchema = z.object({
id: z.number(),
name: z.string(),
description: z.string(),
url: z.string(),
previewUrl: z.string().optional(),
downloadUrl: z.string().optional(),
duration: z.number(),
filesize: z.number(),
type: z.string(),
channels: z.number(),
bitrate: z.number(),
bitdepth: z.number(),
samplerate: z.number(),
username: z.string(),
tags: z.array(z.string()),
license: z.string(),
created: z.string(),
downloads: z.number().optional(),
rating: z.number().optional(),
ratingCount: z.number().optional(),
});
const apiResponseSchema = z.object({
count: z.number(),
next: z.string().nullable(),
previous: z.string().nullable(),
results: z.array(transformedResultSchema),
query: z.string().optional(),
type: z.string(),
page: z.number(),
pageSize: z.number(),
sort: z.string(),
minRating: z.number().optional(),
});
function buildSortParameter({ query, sort }: { query?: string; sort: string }) {
if (!query) return `${sort}_desc`;
return sort === "score" ? "score" : `${sort}_desc`;
}
function applyEffectsFilters({
params,
min_rating,
commercial_only,
}: {
params: URLSearchParams;
min_rating: number;
commercial_only: boolean;
}) {
params.append("filter", "duration:[* TO 30.0]");
params.append("filter", `avg_rating:[${min_rating} TO *]`);
if (commercial_only) {
params.append(
"filter",
'license:("Attribution" OR "Creative Commons 0" OR "Attribution Noncommercial" OR "Attribution Commercial")',
);
}
params.append(
"filter",
"tag:sound-effect OR tag:sfx OR tag:foley OR tag:ambient OR tag:nature OR tag:mechanical OR tag:electronic OR tag:impact OR tag:whoosh OR tag:explosion",
);
}
function transformFreesoundResult(
result: z.infer<typeof freesoundResultSchema>,
) {
return {
id: result.id,
name: result.name,
description: result.description,
url: result.url,
previewUrl:
result.previews?.["preview-hq-mp3"] ||
result.previews?.["preview-lq-mp3"],
downloadUrl: result.download,
duration: result.duration,
filesize: result.filesize,
type: result.type,
channels: result.channels,
bitrate: result.bitrate,
bitdepth: result.bitdepth,
samplerate: result.samplerate,
username: result.username,
tags: result.tags,
license: result.license,
created: result.created,
downloads: result.num_downloads || 0,
rating: result.avg_rating || 0,
ratingCount: result.num_ratings || 0,
};
}
export async function GET(request: NextRequest) {
try {
const { limited } = await checkRateLimit({ request });
if (limited) {
return NextResponse.json({ error: "Too many requests" }, { status: 429 });
}
const { searchParams } = new URL(request.url);
const validationResult = searchParamsSchema.safeParse({
q: searchParams.get("q") || undefined,
type: searchParams.get("type") || undefined,
page: searchParams.get("page") || undefined,
page_size: searchParams.get("page_size") || undefined,
sort: searchParams.get("sort") || undefined,
min_rating: searchParams.get("min_rating") || undefined,
});
if (!validationResult.success) {
return NextResponse.json(
{
error: "Invalid parameters",
details: validationResult.error.flatten().fieldErrors,
},
{ status: 400 },
);
}
const {
q: query,
type,
page,
page_size: pageSize,
sort,
min_rating,
commercial_only,
} = validationResult.data;
if (type === "songs") {
return NextResponse.json(
{
error: "Songs are not available yet",
message:
"Song search functionality is coming soon. Try searching for sound effects instead.",
},
{ status: 501 },
);
}
const baseUrl = "https://freesound.org/apiv2/search/text/";
const sortParam = buildSortParameter({ query, sort });
const params = new URLSearchParams({
query: query || "",
token: webEnv.FREESOUND_API_KEY,
page: page.toString(),
page_size: pageSize.toString(),
sort: sortParam,
fields:
"id,name,description,url,previews,download,duration,filesize,type,channels,bitrate,bitdepth,samplerate,username,tags,license,created,num_downloads,avg_rating,num_ratings",
});
const isEffectsSearch = type === "effects" || !type;
if (isEffectsSearch) {
applyEffectsFilters({ params, min_rating, commercial_only });
}
const response = await fetch(`${baseUrl}?${params.toString()}`);
if (!response.ok) {
const errorText = await response.text();
console.error("Freesound API error:", response.status, errorText);
return NextResponse.json(
{ error: "Failed to search sounds" },
{ status: response.status },
);
}
const rawData = await response.json();
const freesoundValidation = freesoundResponseSchema.safeParse(rawData);
if (!freesoundValidation.success) {
console.error(
"Invalid Freesound API response:",
freesoundValidation.error,
);
return NextResponse.json(
{ error: "Invalid response from Freesound API" },
{ status: 502 },
);
}
const data = freesoundValidation.data;
const transformedResults = data.results.map(transformFreesoundResult);
const responseData = {
count: data.count,
next: data.next,
previous: data.previous,
results: transformedResults,
query: query || "",
type: type || "effects",
page,
pageSize,
sort,
minRating: min_rating,
};
const responseValidation = apiResponseSchema.safeParse(responseData);
if (!responseValidation.success) {
console.error(
"Invalid API response structure:",
responseValidation.error,
);
return NextResponse.json(
{ error: "Internal response formatting error" },
{ status: 500 },
);
}
return NextResponse.json(responseValidation.data);
} catch (error) {
console.error("Error searching sounds:", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 },
);
}
}
+83
View File
@@ -0,0 +1,83 @@
import { NextRequest, NextResponse } from "next/server";
import { db, eq, waitlist } from "@opencut/db";
import { checkBotId } from "botid/server";
import { nanoid } from "nanoid";
import { waitlistRateLimit } from "@/lib/rate-limit";
import { z } from "zod";
import { env } from "@/env";
import { cookies } from "next/headers";
import crypto from "crypto";
const waitlistSchema = z.object({
email: z.string().email("Invalid email format").min(1, "Email is required"),
});
const CSRF_TOKEN_NAME = "waitlist-csrf";
const TOKEN_EXPIRY = 60 * 60 * 1000;
async function validateCSRFToken(request: NextRequest): Promise<boolean> {
const clientToken = request.headers.get("x-csrf-token");
if (!clientToken) return false;
const cookieStore = await cookies();
const cookieValue = cookieStore.get(CSRF_TOKEN_NAME)?.value;
if (!cookieValue) return false;
const [token, timestamp, signature] = cookieValue.split(":");
if (!token || !timestamp || !signature) return false;
if (clientToken !== token) return false;
const now = Date.now();
const tokenTime = parseInt(timestamp);
if (now - tokenTime > TOKEN_EXPIRY) return false;
const expectedSignature = crypto.createHmac("sha256", env.BETTER_AUTH_SECRET).update(`${token}:${timestamp}`).digest("hex");
return signature === expectedSignature;
}
export async function POST(request: NextRequest) {
const verification = await checkBotId();
if (verification.isBot) {
return NextResponse.json({ error: "Access denied" }, { status: 403 });
}
const identifier = request.headers.get("x-forwarded-for") ?? "127.0.0.1";
const { success } = await waitlistRateLimit.limit(identifier);
if (!success) {
return NextResponse.json({ error: "Too many requests. Please try again later." }, { status: 429 });
}
const isValidToken = await validateCSRFToken(request);
if (!isValidToken) {
return NextResponse.json({ error: "Invalid security token" }, { status: 403 });
}
try {
const body = await request.json();
const { email } = waitlistSchema.parse(body);
const existingEmail = await db.select().from(waitlist).where(eq(waitlist.email, email.toLowerCase())).limit(1);
if (existingEmail.length > 0) {
return NextResponse.json({ error: "Email already registered" }, { status: 409 });
}
await db.insert(waitlist).values({
id: nanoid(),
email: email.toLowerCase(),
});
return NextResponse.json({ message: "Successfully joined waitlist!" }, { status: 201 });
} catch (error) {
if (error instanceof z.ZodError) {
const firstError = error.errors[0];
return NextResponse.json({ error: firstError.message }, { status: 400 });
}
console.error("Waitlist signup error:", error);
return NextResponse.json({ error: "Internal server error" }, { status: 500 });
}
}
@@ -0,0 +1,46 @@
import { NextRequest, NextResponse } from "next/server";
import { cookies } from "next/headers";
import crypto from "crypto";
import { env } from "@/env";
const CSRF_TOKEN_NAME = "waitlist-csrf";
const TOKEN_EXPIRY = 60 * 60 * 1000;
const allowedHosts = env.NODE_ENV === "development" ? ["localhost:3000", "127.0.0.1:3000"] : ["opencut.app", "www.opencut.app"];
export async function GET(request: NextRequest) {
const referer = request.headers.get("referer");
const host = request.headers.get("host");
if (referer) {
const refererUrl = new URL(referer);
if (!allowedHosts.some((allowed) => refererUrl.host === allowed || refererUrl.host.endsWith(allowed))) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
} else if (host) {
if (!allowedHosts.some((allowed) => host === allowed || host.endsWith(allowed))) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
} else {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
if (!env.BETTER_AUTH_SECRET) {
throw new Error("BETTER_AUTH_SECRET must be configured");
}
const token = crypto.randomBytes(32).toString("hex");
const timestamp = Date.now();
const signature = crypto.createHmac("sha256", env.BETTER_AUTH_SECRET).update(`${token}:${timestamp}`).digest("hex");
const cookieStore = await cookies();
cookieStore.set(CSRF_TOKEN_NAME, `${token}:${timestamp}:${signature}`, {
httpOnly: true,
secure: env.NODE_ENV === "production",
sameSite: "strict",
maxAge: TOKEN_EXPIRY / 1000,
path: "/",
});
return NextResponse.json({ token });
}
-53
View File
@@ -1,53 +0,0 @@
import { Header } from "@/components/header";
import { Footer } from "@/components/footer";
import { cn } from "@/utils/ui";
interface BasePageProps {
children: React.ReactNode;
className?: string;
mainClassName?: string;
maxWidth?: "3xl" | "6xl" | "full";
title?: string;
description?: string;
}
export function BasePage({
children,
className = "",
mainClassName = "",
maxWidth = "3xl",
title,
description,
}: BasePageProps) {
const maxWidthClass = {
"3xl": "max-w-3xl",
"6xl": "max-w-6xl",
full: "max-w-full",
}[maxWidth];
return (
<section className={cn("bg-background min-h-screen", className)}>
<Header />
<main
className={cn(
"relative container mx-auto flex flex-col gap-12 px-6 pt-12 pb-24 md:pt-24",
maxWidthClass,
mainClassName,
)}
>
{title && description && (
<div className="flex flex-col gap-8 text-center">
<h1 className="text-5xl font-bold tracking-tight md:text-6xl">
{title}
</h1>
<p className="text-muted-foreground mx-auto max-w-2xl text-xl leading-relaxed">
{description}
</p>
</div>
)}
{children}
</main>
<Footer />
</section>
);
}
+123 -131
View File
@@ -1,152 +1,144 @@
import type { Metadata } from "next";
import Image from "next/image";
import { notFound } from "next/navigation";
import { BasePage } from "@/app/base-page";
import { Header } from "@/components/header";
import Prose from "@/components/ui/prose";
import { Separator } from "@/components/ui/separator";
import { getPosts, getSinglePost, processHtmlContent } from "@/lib/blog/query";
import type { Author, Post } from "@/types/blog";
import { getPosts, getSinglePost, processHtmlContent } from "@/lib/blog-query";
import { Metadata } from "next";
import Image from "next/image";
import { notFound } from "next/navigation";
type PageProps = {
params: Promise<{ slug: string }>;
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
params: Promise<{ slug: string }>;
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
};
export async function generateMetadata({
params,
params,
}: PageProps): Promise<Metadata> {
const slug = (await params).slug;
const slug = (await params).slug;
const data = await getSinglePost({ slug });
const data = await getSinglePost(slug);
if (!data || !data.post) return {};
if (!data || !data.post) return {};
return {
title: data.post.title,
description: data.post.description,
twitter: {
title: `${data.post.title}`,
description: `${data.post.description}`,
card: "summary_large_image",
images: [
{
url: data.post.coverImage,
width: "1200",
height: "630",
alt: data.post.title,
},
],
},
openGraph: {
type: "article",
images: [
{
url: data.post.coverImage,
width: "1200",
height: "630",
alt: data.post.title,
},
],
title: data.post.title,
description: data.post.description,
publishedTime: new Date(data.post.publishedAt).toISOString(),
authors: data.post.authors.map((author: Author) => author.name),
},
};
return {
title: data.post.title,
description: data.post.description,
twitter: {
title: `${data.post.title}`,
description: `${data.post.description}`,
card: "summary_large_image",
images: [
{
url: data.post.coverImage,
width: "1200",
height: "630",
alt: data.post.title,
},
],
},
openGraph: {
type: "article",
images: [
{
url: data.post.coverImage,
width: "1200",
height: "630",
alt: data.post.title,
},
],
title: data.post.title,
description: data.post.description,
publishedTime: new Date(data.post.publishedAt).toISOString(),
authors: [
...data.post.authors.map((author: { name: string }) => author.name),
],
},
};
}
export async function generateStaticParams() {
const data = await getPosts();
if (!data || !data.posts.length) return [];
const data = await getPosts();
if (!data || !data.posts.length) return [];
return data.posts.map((post) => ({
slug: post.slug,
}));
return data.posts.map((post) => ({
slug: post.slug,
}));
}
export default async function BlogPostPage({ params }: PageProps) {
const slug = (await params).slug;
const data = await getSinglePost({ slug });
if (!data || !data.post) return notFound();
async function Page({ params }: PageProps) {
const slug = (await params).slug;
const data = await getSinglePost(slug);
if (!data || !data.post) return notFound();
const html = await processHtmlContent({ html: data.post.content });
const html = await processHtmlContent(data.post.content);
return (
<BasePage>
<PostHeader post={data.post} />
<Separator />
<PostContent html={html} />
</BasePage>
);
const formattedDate = new Date(data.post.publishedAt).toLocaleDateString(
"en-US",
{
day: "numeric",
month: "long",
year: "numeric",
}
);
return (
<div className="min-h-screen bg-background">
<Header />
<main className="relative">
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<div className="absolute -top-40 -right-40 w-96 h-96 bg-gradient-to-br from-muted/20 to-transparent rounded-full blur-3xl" />
<div className="absolute top-1/2 -left-40 w-80 h-80 bg-gradient-to-tr from-muted/10 to-transparent rounded-full blur-3xl" />
</div>
<div className="relative container max-w-3xl mx-auto px-4 py-16">
<div className="text-center mb-6">
{data.post.coverImage && (
<div className="relative aspect-video rounded-lg overflow-hidden mb-6">
<Image
src={data.post.coverImage}
alt={data.post.title}
loading="eager"
fill
className="object-cover rounded-lg"
/>
</div>
)}
<div className="flex items-center justify-center mb-6">
<time dateTime={data.post.publishedAt.toString()}>
{formattedDate}
</time>
</div>
<h1 className="text-5xl md:text-4xl font-bold tracking-tight mb-6">
{data.post.title}
</h1>
<div className="flex items-center justify-center gap-2">
{data.post.authors[0] && (
<>
<Image
src={data.post.authors[0].image}
alt={data.post.authors[0].name}
width={36}
height={36}
loading="eager"
className="aspect-square shrink-0 size-8 rounded-full"
/>
<p className="text-muted-foreground">
{data.post.authors[0].name}
</p>
</>
)}
</div>
</div>
<Separator />
<section className="mt-14">
<Prose html={html} />
</section>
</div>
</main>
</div>
);
}
function PostHeader({ post }: { post: Post }) {
const formattedDate = new Date(post.publishedAt).toLocaleDateString("en-US", {
day: "numeric",
month: "long",
year: "numeric",
});
return (
<>
{post.coverImage && <PostCoverImage post={post} />}
<PostMeta date={formattedDate} publishedAt={post.publishedAt} />
<PostTitle title={post.title} />
<PostAuthor author={post.authors[0]} />
</>
);
}
function PostCoverImage({ post }: { post: Post }) {
return (
<div className="relative aspect-video overflow-hidden rounded-lg">
<Image
src={post.coverImage}
alt={post.title}
loading="eager"
fill
className="rounded-lg object-cover"
/>
</div>
);
}
function PostMeta({ date, publishedAt }: { date: string; publishedAt: Date }) {
return (
<div className="flex items-center justify-center">
<time dateTime={publishedAt.toString()}>{date}</time>
</div>
);
}
function PostTitle({ title }: { title: string }) {
return (
<h1 className="text-5xl font-bold tracking-tight md:text-4xl">{title}</h1>
);
}
function PostAuthor({ author }: { author?: Author }) {
if (!author) return null;
return (
<div className="flex items-center justify-center gap-2">
<Image
src={author.image}
alt={author.name}
width={36}
height={36}
loading="eager"
className="aspect-square size-8 shrink-0 rounded-full"
/>
<p className="text-muted-foreground">{author.name}</p>
</div>
);
}
function PostContent({ html }: { html: string }) {
return (
<section className="pt-8">
<Prose html={html} />
</section>
);
}
export default Page;
+88 -63
View File
@@ -1,73 +1,98 @@
import type { Metadata } from "next";
import Link from "next/link";
import { BasePage } from "@/app/base-page";
import { Metadata } from "next";
import { Header } from "@/components/header";
import { Card, CardContent } from "@/components/ui/card";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Separator } from "@/components/ui/separator";
import { getPosts } from "@/lib/blog/query";
import type { Author, Post } from "@/types/blog";
import Link from "next/link";
import { getPosts } from "@/lib/blog-query";
import Image from "next/image";
export const metadata: Metadata = {
title: "Blog - OpenCut",
description:
"Read the latest news and updates about OpenCut, the free and open-source video editor.",
openGraph: {
title: "Blog - OpenCut",
description:
"Read the latest news and updates about OpenCut, the free and open-source video editor.",
type: "website",
},
title: "Blog - OpenCut",
description:
"Read the latest news and updates about OpenCut, the free and open-source video editor.",
openGraph: {
title: "Blog - OpenCut",
description:
"Read the latest news and updates about OpenCut, the free and open-source video editor.",
type: "website",
},
};
export default async function BlogPage() {
const data = await getPosts();
if (!data || !data.posts) return <div>No posts yet</div>;
const data = await getPosts();
if (!data || !data.posts) return <div>No posts yet</div>;
return (
<BasePage
title="Blog"
description="Read the latest news and updates about OpenCut, the free and open-source video editor."
>
<div className="flex flex-col">
{data.posts.map((post) => (
<div key={post.id} className="flex flex-col">
<BlogPostItem post={post} />
<Separator />
</div>
))}
</div>
</BasePage>
);
}
return (
<div className="min-h-screen bg-background">
<Header />
function BlogPostItem({ post }: { post: Post }) {
return (
<Link href={`/blog/${post.slug}`}>
<div className="flex h-auto w-full items-center justify-between py-6 opacity-100 hover:opacity-75">
<div className="flex flex-col gap-2">
<h2 className="text-xl font-semibold">{post.title}</h2>
<p className="text-muted-foreground">{post.description}</p>
</div>
{post.authors && post.authors.length > 0 && (
<AuthorList authors={post.authors} />
)}
</div>
</Link>
);
}
<main className="relative">
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<div className="absolute -top-40 -right-40 w-96 h-96 bg-gradient-to-br from-muted/20 to-transparent rounded-full blur-3xl" />
<div className="absolute top-1/2 -left-40 w-80 h-80 bg-gradient-to-tr from-muted/10 to-transparent rounded-full blur-3xl" />
</div>
function AuthorList({ authors }: { authors: Author[] }) {
return (
<div className="flex items-center gap-2">
{authors.map((author) => (
<div key={author.id} className="flex items-center gap-2">
<Avatar className="size-6 shadow-sm">
<AvatarImage src={author.image} alt={author.name} />
<AvatarFallback className="text-xs">
{author.name.charAt(0).toUpperCase()}
</AvatarFallback>
</Avatar>
</div>
))}
</div>
);
<div className="relative container max-w-3xl mx-auto px-4 py-16">
<div className="text-center mb-20">
<h1 className="text-5xl md:text-6xl font-bold tracking-tight mb-6">
Blog
</h1>
<p className="text-xl text-muted-foreground mb-8 max-w-2xl mx-auto leading-relaxed">
Read the latest news and updates about OpenCut, the free and
open-source video editor.
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{data.posts.map((post) => (
<Link key={post.id} href={`/blog/${post.slug}`}>
<Card className="h-full hover:shadow-lg transition-shadow overflow-hidden">
{post.coverImage && (
<div className="relative aspect-video">
<Image
src={post.coverImage}
alt={post.title}
fill
className="object-cover rounded-xl"
/>
</div>
)}
<CardContent className="p-6">
{post.authors && post.authors.length > 0 && (
<div className="flex items-center gap-2 mb-4">
{post.authors.map((author, index) => (
<div
key={author.id}
className="flex items-center gap-2"
>
<Avatar className="w-6 h-6">
<AvatarImage
src={author.image}
alt={author.name}
/>
<AvatarFallback className="text-xs">
{author.name.charAt(0).toUpperCase()}
</AvatarFallback>
</Avatar>
<span className="text-sm text-muted-foreground">
{author.name}
</span>
{index < post.authors.length - 1 && (
<span className="text-muted-foreground">•</span>
)}
</div>
))}
</div>
)}
<h2 className="text-xl font-semibold mb-2">{post.title}</h2>
<p className="text-muted-foreground">{post.description}</p>
</CardContent>
</Card>
</Link>
))}
</div>
</div>
</main>
</div>
);
}
+277 -242
View File
@@ -1,242 +1,277 @@
import type { Metadata } from "next";
import Link from "next/link";
import { GitHubContributeSection } from "@/components/gitHub-contribute-section";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Card, CardContent } from "@/components/ui/card";
import { EXTERNAL_TOOLS } from "@/constants/site-constants";
import { BasePage } from "../base-page";
export const metadata: Metadata = {
title: "Contributors - OpenCut",
description:
"Meet the amazing people who contribute to OpenCut, the free and open-source video editor.",
openGraph: {
title: "Contributors - OpenCut",
description:
"Meet the amazing people who contribute to OpenCut, the free and open-source video editor.",
type: "website",
},
};
interface Contributor {
id: number;
login: string;
avatar_url: string;
html_url: string;
contributions: number;
type: string;
}
async function getContributors(): Promise<Contributor[]> {
try {
const response = await fetch(
"https://api.github.com/repos/OpenCut-app/OpenCut/contributors?per_page=100",
{
headers: {
Accept: "application/vnd.github.v3+json",
"User-Agent": "OpenCut-Web-App",
},
next: { revalidate: 600 }, // 10 minutes
},
);
if (!response.ok) {
console.error("Failed to fetch contributors");
return [];
}
const contributors = (await response.json()) as Contributor[];
const filteredContributors = contributors.filter(
(contributor) => contributor.type === "User",
);
return filteredContributors;
} catch (error) {
console.error("Error fetching contributors:", error);
return [];
}
}
export default async function ContributorsPage() {
const contributors = await getContributors();
const topContributors = contributors.slice(0, 2);
const otherContributors = contributors.slice(2);
const totalContributions = contributors.reduce(
(sum, c) => sum + c.contributions,
0,
);
return (
<BasePage
title="Contributors"
description="Meet the amazing people who contribute to OpenCut, the free and open-source video editor."
>
<div className="-mt-4 flex items-center justify-center gap-8 text-sm">
<StatItem value={contributors.length} label="contributors" />
<StatItem value={totalContributions} label="contributions" />
</div>
<div className="mx-auto flex max-w-6xl flex-col gap-20">
{topContributors.length > 0 && (
<TopContributorsSection contributors={topContributors} />
)}
{otherContributors.length > 0 && (
<AllContributorsSection contributors={otherContributors} />
)}
<ExternalToolsSection />
<GitHubContributeSection
title="Join the community"
description="OpenCut is built by developers like you. Every contribution, no matter how small, helps make video editing more accessible for everyone."
/>
</div>
</BasePage>
);
}
function StatItem({ value, label }: { value: number; label: string }) {
return (
<div className="flex items-center gap-2">
<div className="bg-foreground size-2 rounded-full" />
<span className="font-medium">{value}</span>
<span className="text-muted-foreground">{label}</span>
</div>
);
}
function TopContributorsSection({
contributors,
}: {
contributors: Contributor[];
}) {
return (
<div className="flex flex-col gap-10">
<div className="flex flex-col gap-2 text-center">
<h2 className="text-2xl font-semibold">Top contributors</h2>
<p className="text-muted-foreground">
Leading the way in contributions
</p>
</div>
<div className="mx-auto flex w-full max-w-xl flex-col justify-center gap-6 md:flex-row">
{contributors.map((contributor) => (
<TopContributorCard key={contributor.id} contributor={contributor} />
))}
</div>
</div>
);
}
function TopContributorCard({ contributor }: { contributor: Contributor }) {
return (
<Link
href={contributor.html_url}
target="_blank"
rel="noopener noreferrer"
className="w-full"
>
<Card>
<CardContent className="flex flex-col gap-6 p-8 text-center">
<Avatar className="mx-auto size-28">
<AvatarImage
src={contributor.avatar_url}
alt={`${contributor.login}'s avatar`}
/>
<AvatarFallback className="text-lg font-semibold">
{contributor.login.charAt(0).toUpperCase()}
</AvatarFallback>
</Avatar>
<div className="flex flex-col gap-2">
<h3 className="text-xl font-semibold">{contributor.login}</h3>
<div className="flex items-center justify-center gap-2">
<span className="font-medium">{contributor.contributions}</span>
<span className="text-muted-foreground">contributions</span>
</div>
</div>
</CardContent>
</Card>
</Link>
);
}
function AllContributorsSection({
contributors,
}: {
contributors: Contributor[];
}) {
return (
<div className="flex flex-col gap-12">
<div className="flex flex-col gap-2 text-center">
<h2 className="text-2xl font-semibold">All contributors</h2>
<p className="text-muted-foreground">
Everyone who makes OpenCut better
</p>
</div>
<div className="grid grid-cols-2 gap-6 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6">
{contributors.map((contributor) => (
<Link
key={contributor.id}
href={contributor.html_url}
target="_blank"
rel="noopener noreferrer"
className="opacity-100 hover:opacity-70"
>
<div className="flex flex-col items-center gap-2 p-2">
<Avatar className="size-16">
<AvatarImage
src={contributor.avatar_url}
alt={`${contributor.login}'s avatar`}
/>
<AvatarFallback>
{contributor.login.charAt(0).toUpperCase()}
</AvatarFallback>
</Avatar>
<div className="text-center">
<h3 className="text-sm font-medium">{contributor.login}</h3>
<p className="text-muted-foreground text-xs">
{contributor.contributions}
</p>
</div>
</div>
</Link>
))}
</div>
</div>
);
}
function ExternalToolsSection() {
return (
<div className="flex flex-col gap-10">
<div className="flex flex-col gap-2 text-center">
<h2 className="text-2xl font-semibold">External tools</h2>
<p className="text-muted-foreground">Tools we use to build OpenCut</p>
</div>
<div className="mx-auto grid max-w-4xl grid-cols-1 gap-6 sm:grid-cols-2">
{EXTERNAL_TOOLS.map((tool, index) => (
<Link
key={tool.url}
href={tool.url}
target="_blank"
className="block"
style={{ animationDelay: `${index * 100}ms` }}
>
<Card className="h-full">
<CardContent className="flex items-center justify-center h-full flex-col gap-4 p-6 text-center">
<tool.icon className="size-8" />
<div className="flex flex-1 flex-col gap-2">
<h3 className="text-lg font-semibold">{tool.name}</h3>
<p className="text-muted-foreground text-sm">
{tool.description}
</p>
</div>
</CardContent>
</Card>
</Link>
))}
</div>
</div>
);
}
import { Metadata } from "next";
import { Header } from "@/components/header";
import { Card, CardContent } from "@/components/ui/card";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import { ExternalLink } from "lucide-react";
import Link from "next/link";
import { GithubIcon } from "@/components/icons";
import { Badge } from "@/components/ui/badge";
export const metadata: Metadata = {
title: "Contributors - OpenCut",
description:
"Meet the amazing people who contribute to OpenCut, the free and open-source video editor.",
openGraph: {
title: "Contributors - OpenCut",
description:
"Meet the amazing people who contribute to OpenCut, the free and open-source video editor.",
type: "website",
},
};
interface Contributor {
id: number;
login: string;
avatar_url: string;
html_url: string;
contributions: number;
type: string;
}
async function getContributors(): Promise<Contributor[]> {
try {
const response = await fetch(
"https://api.github.com/repos/OpenCut-app/OpenCut/contributors?per_page=100",
{
headers: {
Accept: "application/vnd.github.v3+json",
"User-Agent": "OpenCut-Web-App",
},
next: { revalidate: 600 }, // 10 minutes
}
);
if (!response.ok) {
console.error("Failed to fetch contributors");
return [];
}
const contributors = (await response.json()) as Contributor[];
const filteredContributors = contributors.filter(
(contributor: Contributor) => contributor.type === "User"
);
return filteredContributors;
} catch (error) {
console.error("Error fetching contributors:", error);
return [];
}
}
export default async function ContributorsPage() {
const contributors = await getContributors();
const topContributors = contributors.slice(0, 2);
const otherContributors = contributors.slice(2);
return (
<div className="min-h-screen bg-background">
<Header />
<main className="relative">
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<div className="absolute -top-40 -right-40 w-96 h-96 bg-gradient-to-br from-muted/20 to-transparent rounded-full blur-3xl" />
<div className="absolute top-1/2 -left-40 w-80 h-80 bg-gradient-to-tr from-muted/10 to-transparent rounded-full blur-3xl" />
</div>
<div className="relative container mx-auto px-4 py-16">
<div className="max-w-6xl mx-auto">
<div className="text-center mb-20">
<Link
href={"https://github.com/OpenCut-app/OpenCut"}
target="_blank"
>
<Badge variant="secondary" className="gap-2 mb-6">
<GithubIcon className="h-3 w-3" />
Open Source
</Badge>
</Link>
<h1 className="text-5xl md:text-6xl font-bold tracking-tight mb-6">
Contributors
</h1>
<p className="text-xl text-muted-foreground mb-8 max-w-2xl mx-auto leading-relaxed">
Meet the amazing developers who are building the future of video
editing
</p>
<div className="flex items-center justify-center gap-8 text-sm">
<div className="flex items-center gap-2">
<div className="w-2 h-2 bg-foreground rounded-full" />
<span className="font-medium">{contributors.length}</span>
<span className="text-muted-foreground">contributors</span>
</div>
<div className="flex items-center gap-2">
<div className="w-2 h-2 bg-foreground rounded-full" />
<span className="font-medium">
{contributors.reduce((sum, c) => sum + c.contributions, 0)}
</span>
<span className="text-muted-foreground">contributions</span>
</div>
</div>
</div>
{topContributors.length > 0 && (
<div className="mb-20">
<div className="text-center mb-12">
<h2 className="text-2xl font-semibold mb-2">
Top Contributors
</h2>
<p className="text-muted-foreground">
Leading the way in contributions
</p>
</div>
<div className="flex flex-col md:flex-row gap-6 justify-center max-w-4xl mx-auto">
{topContributors.map((contributor, index) => (
<Link
key={contributor.id}
href={contributor.html_url}
target="_blank"
rel="noopener noreferrer"
className="group block flex-1"
>
<div className="relative mx-auto max-w-md">
<div className="absolute inset-0 bg-gradient-to-r from-muted/50 to-muted/30 rounded-2xl blur group-hover:blur-md transition-all duration-300" />
<Card className="relative bg-background/80 backdrop-blur-sm border-2 group-hover:border-muted-foreground/20 transition-all duration-300 group-hover:shadow-xl">
<CardContent className="p-8 text-center">
<div className="relative mb-6">
<Avatar className="h-24 w-24 mx-auto ring-4 ring-background shadow-2xl">
<AvatarImage
src={contributor.avatar_url}
alt={`${contributor.login}'s avatar`}
/>
<AvatarFallback className="text-lg font-semibold">
{contributor.login.charAt(0).toUpperCase()}
</AvatarFallback>
</Avatar>
</div>
<h3 className="text-xl font-semibold mb-2 group-hover:text-foreground/80 transition-colors">
{contributor.login}
</h3>
<div className="flex items-center justify-center gap-2 text-muted-foreground">
<span className="font-medium text-foreground">
{contributor.contributions}
</span>
<span>contributions</span>
</div>
</CardContent>
</Card>
</div>
</Link>
))}
</div>
</div>
)}
{otherContributors.length > 0 && (
<div>
<div className="text-center mb-12">
<h2 className="text-2xl font-semibold mb-2">
All Contributors
</h2>
<p className="text-muted-foreground">
Everyone who makes OpenCut better
</p>
</div>
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-6">
{otherContributors.map((contributor, index) => (
<Link
key={contributor.id}
href={contributor.html_url}
target="_blank"
rel="noopener noreferrer"
className="group block"
style={{
animationDelay: `${index * 50}ms`,
}}
>
<div className="text-center p-2 rounded-xl transition-all duration-300 hover:opacity-50">
<Avatar className="h-16 w-16 mx-auto mb-3">
<AvatarImage
src={contributor.avatar_url}
alt={`${contributor.login}'s avatar`}
/>
<AvatarFallback className="font-medium">
{contributor.login.charAt(0).toUpperCase()}
</AvatarFallback>
</Avatar>
<h3 className="font-medium text-sm truncate group-hover:text-foreground transition-colors mb-1">
{contributor.login}
</h3>
<p className="text-xs text-muted-foreground">
{contributor.contributions}
</p>
</div>
</Link>
))}
</div>
</div>
)}
{contributors.length === 0 && (
<div className="text-center py-20">
<div className="w-20 h-20 mx-auto mb-6 rounded-full bg-muted/50 flex items-center justify-center">
<GithubIcon className="h-8 w-8 text-muted-foreground" />
</div>
<h3 className="text-xl font-medium mb-3">
No contributors found
</h3>
<p className="text-muted-foreground mb-8 max-w-md mx-auto">
Unable to load contributors at the moment. Check back later or
view on GitHub.
</p>
<Link
href="https://github.com/OpenCut-app/OpenCut/graphs/contributors"
target="_blank"
rel="noopener noreferrer"
>
<Button variant="outline" className="gap-2">
<GithubIcon className="h-4 w-4" />
View on GitHub
<ExternalLink className="h-4 w-4" />
</Button>
</Link>
</div>
)}
<div className="mt-32 text-center">
<div className="max-w-2xl mx-auto">
<h2 className="text-3xl font-bold mb-4">Join the community</h2>
<p className="text-lg text-muted-foreground mb-10 leading-relaxed">
OpenCut is built by developers like you. Every contribution,
no matter how small, helps make video editing more accessible
for everyone.
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<Link
href="https://github.com/OpenCut-app/OpenCut/blob/main/.github/CONTRIBUTING.md"
target="_blank"
rel="noopener noreferrer"
>
<Button size="lg" className="gap-2 group">
<GithubIcon className="h-4 w-4 group-hover:scale-110 transition-transform" />
Start Contributing
</Button>
</Link>
<Link
href="https://github.com/OpenCut-app/OpenCut/issues"
target="_blank"
rel="noopener noreferrer"
>
<Button variant="outline" size="lg" className="gap-2 group">
Browse Issues
<ExternalLink className="h-4 w-4 group-hover:scale-110 transition-transform" />
</Button>
</Link>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
);
}
+129 -92
View File
@@ -1,108 +1,145 @@
"use client";
import { useParams } from "next/navigation";
import { useEffect, useRef } from "react";
import { useParams, useRouter } from "next/navigation";
import {
ResizablePanelGroup,
ResizablePanel,
ResizableHandle,
} from "@/components/ui/resizable";
import { AssetsPanel } from "@/components/editor/panels/assets";
import { PropertiesPanel } from "@/components/editor/panels/properties";
import { Timeline } from "@/components/editor/timeline";
import { PreviewPanel } from "@/components/editor/panels/preview";
import { EditorHeader } from "@/components/editor/editor-header";
import { EditorProvider } from "@/components/providers/editor-provider";
import { Onboarding } from "@/components/editor/onboarding";
import { MigrationDialog } from "@/components/editor/dialogs/migration-dialog";
ResizablePanelGroup,
ResizablePanel,
ResizableHandle,
} from "../../../components/ui/resizable";
import { MediaPanel } from "../../../components/editor/media-panel";
import { PropertiesPanel } from "../../../components/editor/properties-panel";
import { Timeline } from "../../../components/editor/timeline";
import { PreviewPanel } from "../../../components/editor/preview-panel";
import { EditorHeader } from "@/components/editor-header";
import { usePanelStore } from "@/stores/panel-store";
import { useProjectStore } from "@/stores/project-store";
import { EditorProvider } from "@/components/editor-provider";
import { usePlaybackControls } from "@/hooks/use-playback-controls";
import { Onboarding } from "@/components/onboarding";
export default function Editor() {
const params = useParams();
const projectId = params.project_id as string;
const {
toolsPanel,
previewPanel,
mainContent,
timeline,
setToolsPanel,
setPreviewPanel,
setMainContent,
setTimeline,
propertiesPanel,
setPropertiesPanel,
} = usePanelStore();
return (
<EditorProvider projectId={projectId}>
<div className="bg-background flex h-screen w-screen flex-col overflow-hidden">
<EditorHeader />
<div className="min-h-0 min-w-0 flex-1">
<EditorLayout />
</div>
<Onboarding />
<MigrationDialog />
</div>
</EditorProvider>
);
}
const { activeProject, loadProject, createNewProject } = useProjectStore();
const params = useParams();
const router = useRouter();
const projectId = params.project_id as string;
const handledProjectIds = useRef<Set<string>>(new Set());
function EditorLayout() {
const { panels, setPanel } = usePanelStore();
usePlaybackControls();
return (
<ResizablePanelGroup
direction="vertical"
className="size-full gap-[0.18rem]"
onLayout={(sizes) => {
setPanel("mainContent", sizes[0] ?? panels.mainContent);
setPanel("timeline", sizes[1] ?? panels.timeline);
}}
>
<ResizablePanel
defaultSize={panels.mainContent}
minSize={30}
maxSize={85}
className="min-h-0"
>
<ResizablePanelGroup
direction="horizontal"
className="size-full gap-[0.19rem] px-3"
onLayout={(sizes) => {
setPanel("tools", sizes[0] ?? panels.tools);
setPanel("preview", sizes[1] ?? panels.preview);
setPanel("properties", sizes[2] ?? panels.properties);
}}
>
<ResizablePanel
defaultSize={panels.tools}
minSize={15}
maxSize={40}
className="min-w-0 rounded-sm"
>
<AssetsPanel />
</ResizablePanel>
useEffect(() => {
const initProject = async () => {
if (!projectId) return;
<ResizableHandle withHandle />
if (activeProject?.id === projectId) {
return;
}
<ResizablePanel
defaultSize={panels.preview}
minSize={30}
className="min-h-0 min-w-0 flex-1"
>
<PreviewPanel />
</ResizablePanel>
if (handledProjectIds.current.has(projectId)) {
return;
}
<ResizableHandle withHandle />
try {
await loadProject(projectId);
} catch (error) {
handledProjectIds.current.add(projectId);
<ResizablePanel
defaultSize={panels.properties}
minSize={15}
maxSize={40}
className="min-w-0 rounded-sm"
>
<PropertiesPanel />
</ResizablePanel>
</ResizablePanelGroup>
</ResizablePanel>
const newProjectId = await createNewProject("Untitled Project");
router.replace(`/editor/${newProjectId}`);
return;
}
};
<ResizableHandle withHandle />
initProject();
}, [projectId, activeProject?.id, loadProject, createNewProject, router]);
<ResizablePanel
defaultSize={panels.timeline}
minSize={15}
maxSize={70}
className="min-h-0 px-3 pb-3"
>
<Timeline />
</ResizablePanel>
</ResizablePanelGroup>
);
return (
<EditorProvider>
<div className="h-screen w-screen flex flex-col bg-background overflow-hidden">
<EditorHeader />
<div className="flex-1 min-h-0 min-w-0">
<ResizablePanelGroup
direction="vertical"
className="h-full w-full gap-[0.18rem]"
>
<ResizablePanel
defaultSize={mainContent}
minSize={30}
maxSize={85}
onResize={setMainContent}
className="min-h-0"
>
{/* Main content area */}
<ResizablePanelGroup
direction="horizontal"
className="h-full w-full gap-[0.19rem] px-2"
>
{/* Tools Panel */}
<ResizablePanel
defaultSize={toolsPanel}
minSize={15}
maxSize={40}
onResize={setToolsPanel}
className="min-w-0"
>
<MediaPanel />
</ResizablePanel>
<ResizableHandle withHandle />
{/* Preview Area */}
<ResizablePanel
defaultSize={previewPanel}
minSize={30}
onResize={setPreviewPanel}
className="min-w-0 min-h-0 flex-1"
>
<PreviewPanel />
</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel
defaultSize={propertiesPanel}
minSize={15}
maxSize={40}
onResize={setPropertiesPanel}
className="min-w-0"
>
<PropertiesPanel />
</ResizablePanel>
</ResizablePanelGroup>
</ResizablePanel>
<ResizableHandle withHandle />
{/* Timeline */}
<ResizablePanel
defaultSize={timeline}
minSize={15}
maxSize={70}
onResize={setTimeline}
className="min-h-0 px-2 pb-2"
>
<Timeline />
</ResizablePanel>
</ResizablePanelGroup>
</div>
<Onboarding />
</div>
</EditorProvider>
);
}
+83 -256
View File
@@ -1,261 +1,88 @@
@import "tailwindcss";
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Custom variant for dark mode */
@custom-variant dark (&:where(.dark, .dark *));
/* Plugins */
@plugin "@tailwindcss/typography";
@plugin "tailwindcss-animate";
:root {
--background: hsl(0, 0%, 100%);
--foreground: hsl(0 0% 11%);
--card: hsl(0, 0%, 100%);
--card-foreground: hsl(0 0% 11%);
--popover: hsl(0, 0%, 100%);
--popover-foreground: hsl(0 0% 2%);
--primary: hsl(203, 100%, 50%);
--primary-hover: hsl(203, 100%, 45%);
--primary-foreground: hsl(0, 0%, 100%);
--secondary: hsl(216 13% 94%);
--secondary-foreground: hsl(0 0% 2%);
--muted: hsl(0 0% 85.1%);
--muted-foreground: hsl(0 0% 50%);
--accent: hsl(216, 13%, 88%);
--accent-foreground: hsl(0 0% 2%);
--destructive: hsl(0, 83%, 50%);
--destructive-foreground: hsl(0, 0%, 100%);
--constructive: hsl(141, 71%, 48%);
--constructive-foreground: hsl(0, 0%, 100%);
--border: hsl(0 0% 88%);
--input: hsl(0 0% 85.1%);
--ring: hsl(0, 0%, 55%);
--chart-1: hsl(220 70% 50%);
--chart-2: hsl(160 60% 45%);
--chart-3: hsl(30 80% 55%);
--chart-4: hsl(280 65% 60%);
--chart-5: hsl(340 75% 55%);
--sidebar-background: hsl(0 0% 96.1%);
--sidebar-foreground: hsl(0 0% 2%);
--sidebar-primary: hsl(0 0% 2%);
--sidebar-primary-foreground: hsl(0 0% 91%);
--sidebar-accent: hsl(0 0% 85.1%);
--sidebar-accent-foreground: hsl(0 0% 2%);
--sidebar-border: hsl(0 0% 85.1%);
--sidebar-ring: hsl(0 0% 16.9%);
--panel-background: hsl(216 13% 94%);
--panel-accent: hsl(216, 8%, 88%);
--sidebar: hsl(0 0% 98%);
}
.dark {
--background: hsl(0 0% 4%);
--foreground: hsl(0 0% 89%);
--card: hsl(0 0% 4%);
--card-foreground: hsl(0 0% 89%);
--popover: hsl(0 0% 14.9%);
--popover-foreground: hsl(0 0% 98%);
--primary: hsl(203, 100%, 50%);
--primary-hover: hsl(203, 100%, 45%);
--primary-foreground: hsl(0 0% 9%);
--secondary: hsl(0 0% 14.9%);
--secondary-foreground: hsl(0 0% 98%);
--muted: hsl(0 0% 14.9%);
--muted-foreground: hsl(0 0% 63.9%);
--accent: hsl(0, 0%, 28%);
--accent-foreground: hsl(0 0% 98%);
--destructive: hsl(0 83%, 55%);
--destructive-foreground: hsl(0 0% 98%);
--constructive: hsl(141, 71%, 48%);
--constructive-foreground: hsl(0 0% 100%);
--border: hsl(0 0% 17%);
--input: hsl(0 0% 14.9%);
--ring: hsl(0 0% 83.1%);
--chart-1: hsl(220 70% 50%);
--chart-2: hsl(160 60% 45%);
--chart-3: hsl(30 80% 55%);
--chart-4: hsl(280 65% 60%);
--chart-5: hsl(340 75% 55%);
--sidebar-background: hsl(0 0% 3.9%);
--sidebar-foreground: hsl(0 0% 98%);
--sidebar-primary: hsl(0 0% 98%);
--sidebar-primary-foreground: hsl(0 0% 9%);
--sidebar-accent: hsl(0 0% 14.9%);
--sidebar-accent-foreground: hsl(0 0% 98%);
--sidebar-border: hsl(0 0% 14.9%);
--sidebar-ring: hsl(0 0% 83.1%);
--panel-background: hsl(0 0% 11%);
--panel-accent: hsl(0 0% 15%);
--sidebar: hsl(240 5.9% 10%);
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--primary: 0 0% 9%;
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--ring: 0 0% 3.9%;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--radius: 1rem;
--sidebar-background: 0 0% 100%;
--sidebar-foreground: 0 0% 3.9%;
--sidebar-primary: 0 0% 9%;
--sidebar-primary-foreground: 0 0% 98%;
--sidebar-accent: 0 0% 96.1%;
--sidebar-accent-foreground: 0 0% 9%;
--sidebar-border: 0 0% 89.8%;
--sidebar-ring: 0 0% 3.9%;
}
.dark {
--background: 0 0% 4%;
--foreground: 0 0% 89%;
--card: 0 0% 14.9%;
--card-foreground: 0 0% 98%;
--popover: 0 0% 14.9%;
--popover-foreground: 0 0% 98%;
--primary: 180 95% 40%;
--primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 100% 60%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 17%;
--input: 0 0% 14.9%;
--ring: 0 0% 83.1%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
--sidebar-background: 0 0% 3.9%;
--sidebar-foreground: 0 0% 98%;
--sidebar-primary: 0 0% 98%;
--sidebar-primary-foreground: 0 0% 9%;
--sidebar-accent: 0 0% 14.9%;
--sidebar-accent-foreground: 0 0% 98%;
--sidebar-border: 0 0% 14.9%;
--sidebar-ring: 0 0% 83.1%;
--panel-background: 0 0% 11%;
--panel-accent: 0 0% 15%;
}
}
@layer base {
/*
The default border color has changed to `currentcolor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentcolor);
}
/* Other default base styles */
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
/* Prevent back/forward swipe */
overscroll-behavior-x: contain;
}
}
@theme inline {
/* Responsive breakpoints */
--breakpoint-xs: 30rem;
/* Typography */
--font-sans: var(--font-inter), sans-serif;
/* Font sizes */
--text-xl: 1.20rem;
--text-base: 0.92rem;
--text-base--line-height: calc(1.5 / 0.95);
--text-xs: 0.75rem;
--text-xs--line-height: calc(1 / 0.8);
/* Border radius */
--radius-lg: 0.82rem;
--radius-md: 0.65rem;
--radius-sm: 0.35rem;
/* Palette mapped to root design tokens */
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-primary-hover: var(--primary-hover);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-constructive: var(--constructive);
--color-constructive-foreground: var(--constructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
/* Chart colors */
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
/* Sidebar */
--color-sidebar: var(--sidebar-background);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
/* Panel */
--color-panel: var(--panel-background);
--color-panel-accent: var(--panel-accent);
/* Animations */
--animate-accordion-down: accordion-down 0.2s ease-out;
--animate-accordion-up: accordion-up 0.2s ease-out;
@keyframes accordion-down {
from {
height: 0;
}
to {
height: var(--radix-accordion-content-height);
}
}
@keyframes accordion-up {
from {
height: var(--radix-accordion-content-height);
}
to {
height: 0;
}
}
}
@utility scrollbar-hidden {
-ms-overflow-style: none;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
@utility scrollbar-x-hidden {
-ms-overflow-style: none;
scrollbar-width: none;
&::-webkit-scrollbar:horizontal {
display: none;
}
}
@utility scrollbar-y-hidden {
-ms-overflow-style: none;
scrollbar-width: none;
&::-webkit-scrollbar:vertical {
display: none;
}
}
@utility scrollbar-thin {
&::-webkit-scrollbar {
width: 6px;
height: 8px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: var(--border);
border-radius: 4px;
}
&::-webkit-scrollbar-thumb:hover {
background: var(--muted-foreground);
}
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
/* Prevent back/forward swipe */
overscroll-behavior-x: contain;
}
}
+38 -41
View File
@@ -1,58 +1,55 @@
import { ThemeProvider } from "next-themes";
import { Analytics } from "@vercel/analytics/react";
import Script from "next/script";
import "./globals.css";
import { Toaster } from "../components/ui/sonner";
import { TooltipProvider } from "../components/ui/tooltip";
import { StorageProvider } from "../components/storage-provider";
import { baseMetaData } from "./metadata";
import { defaultFont } from "../lib/font-config";
import { BotIdClient } from "botid/client";
import { webEnv } from "@opencut/env/web";
import { Inter } from "next/font/google";
const siteFont = Inter({ subsets: ["latin"] });
import { env } from "@/env";
export const metadata = baseMetaData;
const protectedRoutes = [
{
path: "/none",
method: "GET",
},
{
path: "/api/waitlist",
method: "POST",
},
];
export default function RootLayout({
children,
children,
}: Readonly<{
children: React.ReactNode;
children: React.ReactNode;
}>) {
return (
<html lang="en" suppressHydrationWarning>
<head>
<BotIdClient protect={protectedRoutes} />
</head>
<body className={`${siteFont.className} font-sans antialiased`}>
<ThemeProvider
attribute="class"
defaultTheme="system"
disableTransitionOnChange={true}
>
<TooltipProvider>
<Toaster />
<Script
src="https://cdn.databuddy.cc/databuddy.js"
strategy="afterInteractive"
async
data-client-id="UP-Wcoy5arxFeK7oyjMMZ"
data-disabled={webEnv.NODE_ENV === "development"}
data-track-attributes={false}
data-track-errors={true}
data-track-outgoing-links={false}
data-track-web-vitals={false}
data-track-sessions={false}
/>
{children}
</TooltipProvider>
</ThemeProvider>
</body>
</html>
);
return (
<html lang="en" suppressHydrationWarning>
<head>
<BotIdClient protect={protectedRoutes} />
</head>
<body className={`${defaultFont.className} font-sans antialiased`}>
<ThemeProvider attribute="class" forcedTheme="dark">
<TooltipProvider>
<StorageProvider>{children}</StorageProvider>
<Analytics />
<Toaster />
<Script
src="https://cdn.databuddy.cc/databuddy.js"
strategy="afterInteractive"
async
data-client-id="UP-Wcoy5arxFeK7oyjMMZ"
data-disabled={env.NODE_ENV === "development"}
data-track-attributes={false}
data-track-errors={true}
data-track-outgoing-links={false}
data-track-web-vitals={false}
data-track-sessions={false}
/>
</TooltipProvider>
</ThemeProvider>
</body>
</html>
);
}
+88 -83
View File
@@ -1,86 +1,91 @@
import type { Metadata } from "next";
import { SITE_INFO, SITE_URL } from "@/constants/site-constants";
import { Metadata } from "next";
const title = "OpenCut";
const description =
"A simple but powerful video editor that gets the job done. In your browser.";
const openGraphImageUrl = "https://opencut.app/open-graph/default.jpg";
const twitterImageUrl = "/open-graph/default.jpg";
export const baseMetaData: Metadata = {
metadataBase: new URL(SITE_URL),
title: SITE_INFO.title,
description: SITE_INFO.description,
openGraph: {
title: SITE_INFO.title,
description: SITE_INFO.description,
url: SITE_URL,
siteName: SITE_INFO.title,
locale: "en_US",
type: "website",
images: [
{
url: SITE_INFO.openGraphImage,
width: 1200,
height: 630,
alt: "OpenCut Wordmark",
},
],
},
twitter: {
card: "summary_large_image",
title: SITE_INFO.title,
description: SITE_INFO.description,
creator: "@opencutapp",
images: [SITE_INFO.twitterImage],
},
pinterest: {
richPin: false,
},
robots: {
index: true,
follow: true,
},
icons: {
icon: [
{ url: "/favicon.ico" },
{ url: "/icons/favicon-16x16.png", sizes: "16x16", type: "image/png" },
{ url: "/icons/favicon-32x32.png", sizes: "32x32", type: "image/png" },
{ url: "/icons/favicon-96x96.png", sizes: "96x96", type: "image/png" },
],
apple: [
{ url: "/icons/apple-icon-57x57.png", sizes: "57x57", type: "image/png" },
{ url: "/icons/apple-icon-60x60.png", sizes: "60x60", type: "image/png" },
{ url: "/icons/apple-icon-72x72.png", sizes: "72x72", type: "image/png" },
{ url: "/icons/apple-icon-76x76.png", sizes: "76x76", type: "image/png" },
{
url: "/icons/apple-icon-114x114.png",
sizes: "114x114",
type: "image/png",
},
{
url: "/icons/apple-icon-120x120.png",
sizes: "120x120",
type: "image/png",
},
{
url: "/icons/apple-icon-144x144.png",
sizes: "144x144",
type: "image/png",
},
{
url: "/icons/apple-icon-152x152.png",
sizes: "152x152",
type: "image/png",
},
{
url: "/icons/apple-icon-180x180.png",
sizes: "180x180",
type: "image/png",
},
],
shortcut: ["/favicon.ico"],
},
appleWebApp: {
capable: true,
title: SITE_INFO.title,
},
manifest: "/manifest.json",
other: {
"msapplication-config": "/browserconfig.xml",
},
metadataBase: new URL("https://opencut.app"),
title: title,
description: description,
openGraph: {
title: title,
description: description,
url: "https://opencut.app",
siteName: "OpenCut",
locale: "en_US",
type: "website",
images: [
{
url: openGraphImageUrl,
width: 1200,
height: 630,
alt: "OpenCut",
},
],
},
twitter: {
card: "summary_large_image",
title: title,
description: description,
creator: "@opencutapp",
images: [twitterImageUrl],
},
pinterest: {
richPin: false,
},
robots: {
index: true,
follow: true,
},
icons: {
icon: [
{ url: "/favicon.ico" },
{ url: "/icons/favicon-16x16.png", sizes: "16x16", type: "image/png" },
{ url: "/icons/favicon-32x32.png", sizes: "32x32", type: "image/png" },
{ url: "/icons/favicon-96x96.png", sizes: "96x96", type: "image/png" },
],
apple: [
{ url: "/icons/apple-icon-57x57.png", sizes: "57x57", type: "image/png" },
{ url: "/icons/apple-icon-60x60.png", sizes: "60x60", type: "image/png" },
{ url: "/icons/apple-icon-72x72.png", sizes: "72x72", type: "image/png" },
{ url: "/icons/apple-icon-76x76.png", sizes: "76x76", type: "image/png" },
{
url: "/icons/apple-icon-114x114.png",
sizes: "114x114",
type: "image/png",
},
{
url: "/icons/apple-icon-120x120.png",
sizes: "120x120",
type: "image/png",
},
{
url: "/icons/apple-icon-144x144.png",
sizes: "144x144",
type: "image/png",
},
{
url: "/icons/apple-icon-152x152.png",
sizes: "152x152",
type: "image/png",
},
{
url: "/icons/apple-icon-180x180.png",
sizes: "180x180",
type: "image/png",
},
],
shortcut: ["/favicon.ico"],
},
appleWebApp: {
capable: true,
title: title,
},
manifest: "/manifest.json",
other: {
"msapplication-config": "/browserconfig.xml",
},
};
+7 -15
View File
@@ -1,21 +1,13 @@
import { Hero } from "@/components/landing/hero";
import { Header } from "@/components/header";
import { Footer } from "@/components/footer";
import type { Metadata } from "next";
import { SITE_URL } from "@/constants/site-constants";
export const metadata: Metadata = {
alternates: {
canonical: SITE_URL,
},
};
export default async function Home() {
return (
<div>
<Header />
<Hero />
<Footer />
</div>
);
return (
<div>
<Header />
<Hero />
<Footer />
</div>
);
}
+253 -287
View File
@@ -1,287 +1,253 @@
import type { Metadata } from "next";
import { BasePage } from "@/app/base-page";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { Separator } from "@/components/ui/separator";
import { SOCIAL_LINKS } from "@/constants/site-constants";
export const metadata: Metadata = {
title: "Privacy Policy - OpenCut",
description:
"Learn how OpenCut handles your data and privacy. Our commitment to protecting your information while you edit videos.",
openGraph: {
title: "Privacy Policy - OpenCut",
description:
"Learn how OpenCut handles your data and privacy. Our commitment to protecting your information while you edit videos.",
type: "website",
},
};
export default function PrivacyPage() {
return (
<BasePage
title="Privacy policy"
description="Learn how we handle your data and privacy. Contact us if you have any questions."
>
<Accordion type="single" collapsible className="w-full">
<AccordionItem
value="quick-summary"
className="rounded-2xl border px-5"
>
<AccordionTrigger className="no-underline!">
Quick summary
</AccordionTrigger>
<AccordionContent>
<h3 className="mb-3 text-lg font-medium">
Your content stays private and encrypted.
</h3>
<ol className="list-decimal space-y-2 pl-6">
<li>
Basic editing happens locally in your browser - we never see
your files
</li>
<li>
AI features require encrypted uploads - your content is
encrypted before leaving your device
</li>
<li>
We only collect your email and basic profile info for your
account
</li>
<li>Project data stays on your device, not our servers</li>
<li>
We use analytics to improve the app, but no personal video
content is tracked
</li>
<li>
You can delete your account anytime and all data gets removed
</li>
<li>We don't sell your data or share it with advertisers</li>
</ol>
<p className="mt-4">
Questions? Email us at{" "}
<a
href="mailto:oss@opencut.app"
className="text-primary hover:underline"
>
oss@opencut.app
</a>
</p>
</AccordionContent>
</AccordionItem>
</Accordion>
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">How We Handle Your Content</h2>
<p>
<strong>Basic video editing happens locally on your device.</strong>{" "}
For standard editing features, we never upload, store, or have access
to your video files. Your content remains completely private and under
your control.
</p>
<p>
<strong>AI features require secure processing:</strong> When you
choose to use AI features like auto captions, your audio/video content
is encrypted on your device before being uploaded to our servers for
processing. We use zero-knowledge encryption, meaning we cannot
decrypt or view your content.
</p>
<p>
After AI processing is complete, the encrypted content is immediately
deleted from our servers. Only the results (like generated captions)
are returned to your device.
</p>
</section>
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">Account Information</h2>
<p>When you create an account, we only collect:</p>
<ul className="list-disc space-y-2 pl-6">
<li>Email address (for account access)</li>
<li>
Profile information from Google OAuth (if you choose to sign in with
Google)
</li>
</ul>
<p>
<strong>We do NOT store your projects on our servers.</strong> All
project data, including names, thumbnails, and creation dates, is
stored locally in your browser using IndexedDB.
</p>
<p>
We use{" "}
<a
href="https://www.better-auth.com"
target="_blank"
rel="noopener"
className="text-primary hover:underline"
>
Better Auth
</a>{" "}
for secure authentication and follow industry-standard security
practices.
</p>
</section>
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">AI Features & Encryption</h2>
<p>
When you use AI-powered features (like auto captions, content
analysis, or enhancement tools), your content needs to be processed on
our servers. Here's how we protect your privacy:
</p>
<ul className="list-disc space-y-2 pl-6">
<li>
<strong>Client-side encryption:</strong> Your content is encrypted
on your device before upload
</li>
<li>
<strong>Zero-knowledge processing:</strong> We cannot decrypt or
view your original content
</li>
<li>
<strong>Temporary processing:</strong> Encrypted content is deleted
immediately after processing
</li>
<li>
<strong>Opt-in only:</strong> AI features are optional - basic
editing remains fully local
</li>
</ul>
<p>
Different AI features may process different types of content (audio
for captions, video for analysis, etc.), but all follow the same
zero-knowledge encryption approach.
</p>
</section>
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">Analytics</h2>
<p>
We use{" "}
<a
href="https://www.databuddy.cc"
target="_blank"
rel="noopener"
className="text-primary hover:underline"
>
Databuddy
</a>{" "}
for completely anonymized and non-invasive analytics to understand how
people use OpenCut.
</p>
<p>
This helps us improve the editor, but we never collect personal
information, track individual users, or store any data that could
identify you.
</p>
</section>
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">Local Storage & Cookies</h2>
<p>We use browser local storage and IndexedDB to:</p>
<ul className="list-disc space-y-2 pl-6">
<li>Save your projects locally on your device</li>
<li>Remember your editor preferences and settings</li>
<li>Keep you logged in across browser sessions</li>
</ul>
<p>
All data stays on your device and can be cleared at any time through
your browser settings.
</p>
</section>
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">Third-Party Services</h2>
<p>OpenCut integrates with these services:</p>
<ul className="list-disc space-y-2 pl-6">
<li>
<strong>Google OAuth:</strong> For optional Google sign-in (governed
by Google's privacy policy)
</li>
<li>
<strong>Vercel:</strong> For hosting and content delivery
</li>
<li>
<strong>Databuddy:</strong> For anonymized analytics
</li>
</ul>
</section>
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">Your Rights</h2>
<p>You have complete control over your data:</p>
<ul className="list-disc space-y-2 pl-6">
<li>Delete your account and all associated data at any time</li>
<li>Export your project data</li>
<li>Clear local storage to remove all saved projects</li>
<li>Contact us with any privacy concerns</li>
</ul>
</section>
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">Open Source Transparency</h2>
<p>
OpenCut is completely open source. You can review our code, see
exactly how we handle data, and even self-host the application if you
prefer.
</p>
<p>
View our source code on{" "}
<a
href={SOCIAL_LINKS.github}
target="_blank"
rel="noopener"
className="text-primary hover:underline"
>
GitHub
</a>
.
</p>
</section>
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">Contact Us</h2>
<p>Questions about this privacy policy or how we handle your data?</p>
<p>
Open an issue on our{" "}
<a
href={`${SOCIAL_LINKS.github}/issues`}
target="_blank"
rel="noopener"
className="text-primary hover:underline"
>
GitHub repository
</a>
, email us at{" "}
<a
href="mailto:oss@opencut.app"
className="text-primary hover:underline"
>
oss@opencut.app
</a>
, or reach out on{" "}
<a
href={SOCIAL_LINKS.x}
target="_blank"
rel="noopener"
className="text-primary hover:underline"
>
X (Twitter)
</a>
.
</p>
</section>
<Separator />
<p className="text-muted-foreground text-sm">
Last updated: July 14, 2025
</p>
</BasePage>
);
}
import { Metadata } from "next";
import { Card, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { GithubIcon } from "@/components/icons";
import Link from "next/link";
import { Footer } from "@/components/footer";
import { Header } from "@/components/header";
export const metadata: Metadata = {
title: "Privacy Policy - OpenCut",
description:
"Learn how OpenCut handles your data and privacy. Our commitment to protecting your information while you edit videos.",
openGraph: {
title: "Privacy Policy - OpenCut",
description:
"Learn how OpenCut handles your data and privacy. Our commitment to protecting your information while you edit videos.",
type: "website",
},
};
export default function PrivacyPage() {
return (
<div className="min-h-screen bg-background">
<Header />
<main className="relative">
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<div className="absolute -top-40 -right-40 w-96 h-96 bg-gradient-to-br from-muted/20 to-transparent rounded-full blur-3xl" />
<div className="absolute top-1/2 -left-40 w-80 h-80 bg-gradient-to-tr from-muted/10 to-transparent rounded-full blur-3xl" />
</div>
<div className="relative container mx-auto px-4 py-16">
<div className="max-w-4xl mx-auto">
<div className="text-center mb-10">
<Link
href="https://github.com/OpenCut-app/OpenCut"
target="_blank"
>
<Badge variant="secondary" className="gap-2 mb-6">
<GithubIcon className="h-3 w-3" />
Open Source
</Badge>
</Link>
<h1 className="text-5xl md:text-6xl font-bold tracking-tight mb-6">
Privacy Policy
</h1>
<p className="text-xl text-muted-foreground mb-8 max-w-2xl mx-auto leading-relaxed">
Learn how we handle your data and privacy. Contact us if you
have any questions.
</p>
</div>
<Card className="bg-background/80 backdrop-blur-sm border-2 border-muted/30">
<CardContent className="p-8 text-base leading-relaxed space-y-8">
<section>
<h2 className="text-2xl font-semibold mb-4">
Your Videos Stay Private
</h2>
<p className="mb-4">
<strong>
OpenCut processes all videos locally on your device.
</strong>{" "}
We never upload, store, or have access to your video files.
Your content remains completely private and under your
control at all times.
</p>
<p>
All video editing, rendering, and processing happens in your
browser using WebAssembly and local storage. No video data
is transmitted to our servers.
</p>
</section>
<section>
<h2 className="text-2xl font-semibold mb-4">
Account Information
</h2>
<p className="mb-4">
When you create an account, we only collect:
</p>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li>Email address (for account access)</li>
<li>
Profile information from Google OAuth (if you choose to
sign in with Google)
</li>
</ul>
<p className="mb-4">
<strong>
We do NOT store your projects on our servers.
</strong>{" "}
All project data, including names, thumbnails, and creation
dates, is stored locally in your browser using IndexedDB.
</p>
<p>
We use{" "}
<a
href="https://www.better-auth.com"
target="_blank"
rel="noopener"
className="text-primary hover:underline"
>
Better Auth
</a>{" "}
for secure authentication and follow industry-standard
security practices.
</p>
</section>
<section>
<h2 className="text-2xl font-semibold mb-4">Analytics</h2>
<p className="mb-4">
We use{" "}
<a
href="https://www.databuddy.cc"
target="_blank"
rel="noopener"
className="text-primary hover:underline"
>
Databuddy
</a>{" "}
for completely anonymized and non-invasive analytics to
understand how people use OpenCut.
</p>
<p>
This helps us improve the editor, but we never collect
personal information, track individual users, or store any
data that could identify you.
</p>
</section>
<section>
<h2 className="text-2xl font-semibold mb-4">
Local Storage & Cookies
</h2>
<p className="mb-4">
We use browser local storage and IndexedDB to:
</p>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li>Save your projects locally on your device</li>
<li>Remember your editor preferences and settings</li>
<li>Keep you logged in across browser sessions</li>
</ul>
<p>
All data stays on your device and can be cleared at any time
through your browser settings.
</p>
</section>
<section>
<h2 className="text-2xl font-semibold mb-4">
Third-Party Services
</h2>
<p className="mb-4">
OpenCut integrates with these services:
</p>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li>
<strong>Google OAuth:</strong> For optional Google sign-in
(governed by Google's privacy policy)
</li>
<li>
<strong>Vercel:</strong> For hosting and content delivery
</li>
<li>
<strong>Databuddy:</strong> For anonymized analytics
</li>
</ul>
</section>
<section>
<h2 className="text-2xl font-semibold mb-4">Your Rights</h2>
<p className="mb-4">
You have complete control over your data:
</p>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li>
Delete your account and all associated data at any time
</li>
<li>Export your project data</li>
<li>Clear local storage to remove all saved projects</li>
<li>Contact us with any privacy concerns</li>
</ul>
</section>
<section>
<h2 className="text-2xl font-semibold mb-4">
Open Source Transparency
</h2>
<p className="mb-4">
OpenCut is completely open source. You can review our code,
see exactly how we handle data, and even self-host the
application if you prefer.
</p>
<p>
View our source code on{" "}
<a
href="https://github.com/OpenCut-app/OpenCut"
target="_blank"
rel="noopener"
className="text-primary hover:underline"
>
GitHub
</a>
.
</p>
</section>
<section>
<h2 className="text-2xl font-semibold mb-4">Contact Us</h2>
<p className="mb-4">
Questions about this privacy policy or how we handle your
data?
</p>
<p>
Open an issue on our{" "}
<a
href="https://github.com/OpenCut-app/OpenCut/issues"
target="_blank"
rel="noopener"
className="text-primary hover:underline"
>
GitHub repository
</a>
, email us at{" "}
<a
href="mailto:oss@opencut.app"
className="text-primary hover:underline"
>
oss@opencut.app
</a>
, or reach out on{" "}
<a
href="https://x.com/opencutapp"
target="_blank"
rel="noopener"
className="text-primary hover:underline"
>
X (Twitter)
</a>
.
</p>
</section>
<p className="text-sm text-muted-foreground mt-8 pt-8 border-t border-muted/20">
Last updated: July 14, 2025
</p>
</CardContent>
</Card>
</div>
</div>
</main>
<Footer />
</div>
);
}
File diff suppressed because it is too large Load Diff

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