├── .editorconfig
├── .env.example
├── .gitattributes
├── .gitignore
├── README.md
├── Screenshots
├── Showcase-1.gif
└── screen-1.png
├── app
├── Console
│ └── Commands
│ │ └── GenerateUUIDs.php
├── Helpers
│ └── general.php
├── Http
│ ├── Controllers
│ │ ├── Admin
│ │ │ ├── AdminController.php
│ │ │ └── GitHubController.php
│ │ ├── Auth
│ │ │ ├── AuthenticatedSessionController.php
│ │ │ ├── ConfirmablePasswordController.php
│ │ │ ├── EmailVerificationNotificationController.php
│ │ │ ├── EmailVerificationPromptController.php
│ │ │ ├── NewPasswordController.php
│ │ │ ├── PasswordController.php
│ │ │ ├── PasswordResetLinkController.php
│ │ │ ├── RegisteredUserController.php
│ │ │ └── VerifyEmailController.php
│ │ ├── CommentController.php
│ │ ├── Controller.php
│ │ ├── CreativeController.php
│ │ ├── ItemController.php
│ │ ├── PagesController.php
│ │ ├── ProfileController.php
│ │ └── ProjectController.php
│ ├── Middleware
│ │ └── Admin.php
│ └── Requests
│ │ ├── Auth
│ │ └── LoginRequest.php
│ │ └── ProfileUpdateRequest.php
├── Jobs
│ ├── ItemNewStatusNotification.php
│ └── SendNewCommentNotification.php
├── Livewire
│ └── ItemStatusSelector.php
├── Mail
│ ├── ItemNewStatus.php
│ └── NewItemComment.php
├── Models
│ ├── Comment.php
│ ├── Creative.php
│ ├── Destination.php
│ ├── Item.php
│ ├── Project.php
│ └── User.php
├── Policies
│ └── ItemPolicy.php
├── Providers
│ └── AppServiceProvider.php
├── Services
│ ├── ChatGPTService.php
│ ├── ItemUpvoteService.php
│ └── QueueWorker.php
└── View
│ └── Components
│ ├── AppLayout.php
│ ├── GuestLayout.php
│ └── Head
│ └── tinymceConfig.php
├── artisan
├── bootstrap
├── app.php
├── cache
│ └── .gitignore
└── providers.php
├── composer.json
├── composer.lock
├── config
├── app.php
├── auth.php
├── cache.php
├── database.php
├── filesystems.php
├── livewire.php
├── logging.php
├── mail.php
├── openai.php
├── queue.php
├── services.php
└── session.php
├── database
├── .gitignore
├── factories
│ └── UserFactory.php
├── migrations
│ ├── 0001_01_01_000000_create_users_table.php
│ ├── 0001_01_01_000001_create_cache_table.php
│ ├── 0001_01_01_000002_create_jobs_table.php
│ ├── 2025_01_28_155420_create_projects_table.php
│ ├── 2025_01_28_155641_create_items_table.php
│ ├── 2025_01_28_193840_add_template_to_projects_table.php
│ ├── 2025_01_28_215636_create_comments_table.php
│ ├── 2025_02_06_223953_add_translated_column_to_items_table.php
│ ├── 2025_02_12_092747_change_column_in_items_table.php
│ ├── 2025_02_14_200600_add_uuid_to_items_table.php
│ ├── 2025_02_22_111529_create_destinations_table.php
│ ├── 2025_03_08_212040_create_creatives_table.php
│ └── 2025_03_20_170310_add_github_to_users_table.php
└── seeders
│ └── DatabaseSeeder.php
├── lang
├── de
│ ├── auth.php
│ ├── items.php
│ ├── pagination.php
│ ├── passwords.php
│ ├── profile.php
│ ├── projects.php
│ └── validation.php
└── en
│ ├── auth.php
│ ├── items.php
│ ├── pagination.php
│ ├── passwords.php
│ ├── profile.php
│ ├── projects.php
│ └── validation.php
├── package-lock.json
├── package.json
├── phpunit.xml
├── postcss.config.js
├── public
├── .htaccess
├── assets
│ └── dropzone-5.9.3
│ │ ├── dropzone.min.css
│ │ └── dropzone.min.js
├── favicon.png
├── images
│ └── project-default.png
├── index.php
├── js
│ └── tinymce
│ │ ├── .npmignore
│ │ ├── README.md
│ │ ├── bower.json
│ │ ├── composer.json
│ │ ├── icons
│ │ └── default
│ │ │ ├── icons.js
│ │ │ ├── icons.min.js
│ │ │ └── index.js
│ │ ├── license.md
│ │ ├── models
│ │ └── dom
│ │ │ ├── index.js
│ │ │ ├── model.js
│ │ │ └── model.min.js
│ │ ├── package.json
│ │ ├── plugins
│ │ ├── accordion
│ │ │ ├── index.js
│ │ │ ├── plugin.js
│ │ │ └── plugin.min.js
│ │ ├── advlist
│ │ │ ├── index.js
│ │ │ ├── plugin.js
│ │ │ └── plugin.min.js
│ │ ├── anchor
│ │ │ ├── index.js
│ │ │ ├── plugin.js
│ │ │ └── plugin.min.js
│ │ ├── autolink
│ │ │ ├── index.js
│ │ │ ├── plugin.js
│ │ │ └── plugin.min.js
│ │ ├── autoresize
│ │ │ ├── index.js
│ │ │ ├── plugin.js
│ │ │ └── plugin.min.js
│ │ ├── autosave
│ │ │ ├── index.js
│ │ │ ├── plugin.js
│ │ │ └── plugin.min.js
│ │ ├── charmap
│ │ │ ├── index.js
│ │ │ ├── plugin.js
│ │ │ └── plugin.min.js
│ │ ├── code
│ │ │ ├── index.js
│ │ │ ├── plugin.js
│ │ │ └── plugin.min.js
│ │ ├── codesample
│ │ │ ├── index.js
│ │ │ ├── plugin.js
│ │ │ └── plugin.min.js
│ │ ├── directionality
│ │ │ ├── index.js
│ │ │ ├── plugin.js
│ │ │ └── plugin.min.js
│ │ ├── emoticons
│ │ │ ├── index.js
│ │ │ ├── js
│ │ │ │ ├── emojiimages.js
│ │ │ │ ├── emojiimages.min.js
│ │ │ │ ├── emojis.js
│ │ │ │ └── emojis.min.js
│ │ │ ├── plugin.js
│ │ │ └── plugin.min.js
│ │ ├── fullscreen
│ │ │ ├── index.js
│ │ │ ├── plugin.js
│ │ │ └── plugin.min.js
│ │ ├── help
│ │ │ ├── index.js
│ │ │ ├── js
│ │ │ │ └── i18n
│ │ │ │ │ └── keynav
│ │ │ │ │ ├── ar.js
│ │ │ │ │ ├── bg_BG.js
│ │ │ │ │ ├── ca.js
│ │ │ │ │ ├── cs.js
│ │ │ │ │ ├── da.js
│ │ │ │ │ ├── de.js
│ │ │ │ │ ├── el.js
│ │ │ │ │ ├── en.js
│ │ │ │ │ ├── es.js
│ │ │ │ │ ├── eu.js
│ │ │ │ │ ├── fa.js
│ │ │ │ │ ├── fi.js
│ │ │ │ │ ├── fr_FR.js
│ │ │ │ │ ├── he_IL.js
│ │ │ │ │ ├── hi.js
│ │ │ │ │ ├── hr.js
│ │ │ │ │ ├── hu_HU.js
│ │ │ │ │ ├── id.js
│ │ │ │ │ ├── it.js
│ │ │ │ │ ├── ja.js
│ │ │ │ │ ├── kk.js
│ │ │ │ │ ├── ko_KR.js
│ │ │ │ │ ├── ms.js
│ │ │ │ │ ├── nb_NO.js
│ │ │ │ │ ├── nl.js
│ │ │ │ │ ├── pl.js
│ │ │ │ │ ├── pt_BR.js
│ │ │ │ │ ├── pt_PT.js
│ │ │ │ │ ├── ro.js
│ │ │ │ │ ├── ru.js
│ │ │ │ │ ├── sk.js
│ │ │ │ │ ├── sl_SI.js
│ │ │ │ │ ├── sv_SE.js
│ │ │ │ │ ├── th_TH.js
│ │ │ │ │ ├── tr.js
│ │ │ │ │ ├── uk.js
│ │ │ │ │ ├── vi.js
│ │ │ │ │ ├── zh_CN.js
│ │ │ │ │ └── zh_TW.js
│ │ │ ├── plugin.js
│ │ │ └── plugin.min.js
│ │ ├── image
│ │ │ ├── index.js
│ │ │ ├── plugin.js
│ │ │ └── plugin.min.js
│ │ ├── importcss
│ │ │ ├── index.js
│ │ │ ├── plugin.js
│ │ │ └── plugin.min.js
│ │ ├── insertdatetime
│ │ │ ├── index.js
│ │ │ ├── plugin.js
│ │ │ └── plugin.min.js
│ │ ├── link
│ │ │ ├── index.js
│ │ │ ├── plugin.js
│ │ │ └── plugin.min.js
│ │ ├── lists
│ │ │ ├── index.js
│ │ │ ├── plugin.js
│ │ │ └── plugin.min.js
│ │ ├── media
│ │ │ ├── index.js
│ │ │ ├── plugin.js
│ │ │ └── plugin.min.js
│ │ ├── nonbreaking
│ │ │ ├── index.js
│ │ │ ├── plugin.js
│ │ │ └── plugin.min.js
│ │ ├── pagebreak
│ │ │ ├── index.js
│ │ │ ├── plugin.js
│ │ │ └── plugin.min.js
│ │ ├── preview
│ │ │ ├── index.js
│ │ │ ├── plugin.js
│ │ │ └── plugin.min.js
│ │ ├── quickbars
│ │ │ ├── index.js
│ │ │ ├── plugin.js
│ │ │ └── plugin.min.js
│ │ ├── save
│ │ │ ├── index.js
│ │ │ ├── plugin.js
│ │ │ └── plugin.min.js
│ │ ├── searchreplace
│ │ │ ├── index.js
│ │ │ ├── plugin.js
│ │ │ └── plugin.min.js
│ │ ├── table
│ │ │ ├── index.js
│ │ │ ├── plugin.js
│ │ │ └── plugin.min.js
│ │ ├── visualblocks
│ │ │ ├── index.js
│ │ │ ├── plugin.js
│ │ │ └── plugin.min.js
│ │ ├── visualchars
│ │ │ ├── index.js
│ │ │ ├── plugin.js
│ │ │ └── plugin.min.js
│ │ └── wordcount
│ │ │ ├── index.js
│ │ │ ├── plugin.js
│ │ │ └── plugin.min.js
│ │ ├── skins
│ │ ├── content
│ │ │ ├── dark
│ │ │ │ ├── content.css
│ │ │ │ ├── content.js
│ │ │ │ └── content.min.css
│ │ │ ├── default
│ │ │ │ ├── content.css
│ │ │ │ ├── content.js
│ │ │ │ └── content.min.css
│ │ │ ├── document
│ │ │ │ ├── content.css
│ │ │ │ ├── content.js
│ │ │ │ └── content.min.css
│ │ │ ├── tinymce-5-dark
│ │ │ │ ├── content.css
│ │ │ │ ├── content.js
│ │ │ │ └── content.min.css
│ │ │ ├── tinymce-5
│ │ │ │ ├── content.css
│ │ │ │ ├── content.js
│ │ │ │ └── content.min.css
│ │ │ └── writer
│ │ │ │ ├── content.css
│ │ │ │ ├── content.js
│ │ │ │ └── content.min.css
│ │ └── ui
│ │ │ ├── oxide-dark
│ │ │ ├── content.css
│ │ │ ├── content.inline.css
│ │ │ ├── content.inline.js
│ │ │ ├── content.inline.min.css
│ │ │ ├── content.js
│ │ │ ├── content.min.css
│ │ │ ├── skin.css
│ │ │ ├── skin.js
│ │ │ ├── skin.min.css
│ │ │ ├── skin.shadowdom.css
│ │ │ ├── skin.shadowdom.js
│ │ │ └── skin.shadowdom.min.css
│ │ │ ├── oxide
│ │ │ ├── content.css
│ │ │ ├── content.inline.css
│ │ │ ├── content.inline.js
│ │ │ ├── content.inline.min.css
│ │ │ ├── content.js
│ │ │ ├── content.min.css
│ │ │ ├── skin.css
│ │ │ ├── skin.js
│ │ │ ├── skin.min.css
│ │ │ ├── skin.shadowdom.css
│ │ │ ├── skin.shadowdom.js
│ │ │ └── skin.shadowdom.min.css
│ │ │ ├── tinymce-5-dark
│ │ │ ├── content.css
│ │ │ ├── content.inline.css
│ │ │ ├── content.inline.js
│ │ │ ├── content.inline.min.css
│ │ │ ├── content.js
│ │ │ ├── content.min.css
│ │ │ ├── skin.css
│ │ │ ├── skin.js
│ │ │ ├── skin.min.css
│ │ │ ├── skin.shadowdom.css
│ │ │ ├── skin.shadowdom.js
│ │ │ └── skin.shadowdom.min.css
│ │ │ └── tinymce-5
│ │ │ ├── content.css
│ │ │ ├── content.inline.css
│ │ │ ├── content.inline.js
│ │ │ ├── content.inline.min.css
│ │ │ ├── content.js
│ │ │ ├── content.min.css
│ │ │ ├── skin.css
│ │ │ ├── skin.js
│ │ │ ├── skin.min.css
│ │ │ ├── skin.shadowdom.css
│ │ │ ├── skin.shadowdom.js
│ │ │ └── skin.shadowdom.min.css
│ │ ├── themes
│ │ └── silver
│ │ │ ├── index.js
│ │ │ ├── theme.js
│ │ │ └── theme.min.js
│ │ ├── tinymce.d.ts
│ │ ├── tinymce.js
│ │ └── tinymce.min.js
└── robots.txt
├── resources
├── css
│ └── app.css
├── js
│ ├── app.js
│ └── bootstrap.js
└── views
│ ├── admin
│ └── index.blade.php
│ ├── auth
│ ├── confirm-password.blade.php
│ ├── forgot-password.blade.php
│ ├── login.blade.php
│ ├── register.blade.php
│ ├── reset-password.blade.php
│ └── verify-email.blade.php
│ ├── components
│ ├── auth-session-status.blade.php
│ ├── author-info.blade.php
│ ├── box.blade.php
│ ├── checkbox-group.blade.php
│ ├── danger-button.blade.php
│ ├── dropdown-link.blade.php
│ ├── dropdown.blade.php
│ ├── forms
│ │ └── tinymce-editor.blade.php
│ ├── h1.blade.php
│ ├── h2.blade.php
│ ├── head
│ │ └── tinymce-config.blade.php
│ ├── icons
│ │ ├── app.blade.php
│ │ ├── comments.blade.php
│ │ ├── delete.blade.php
│ │ ├── edit.blade.php
│ │ ├── items.blade.php
│ │ ├── markdown.blade.php
│ │ ├── right-arrow.blade.php
│ │ ├── up-arrow-green.blade.php
│ │ └── up-arrow.blade.php
│ ├── input-error.blade.php
│ ├── input-group.blade.php
│ ├── input-label.blade.php
│ ├── item-priority.blade.php
│ ├── item-table.blade.php
│ ├── item-type.blade.php
│ ├── modal.blade.php
│ ├── nav-link.blade.php
│ ├── primary-button.blade.php
│ ├── primary-link-button.blade.php
│ ├── responsive-nav-link.blade.php
│ ├── secondary-button.blade.php
│ ├── select-group.blade.php
│ ├── text-input.blade.php
│ ├── textarea-group.blade.php
│ └── textarea.blade.php
│ ├── emails
│ ├── item-new-comment.blade.php
│ └── item-new-status.blade.php
│ ├── errors
│ ├── 401.blade.php
│ ├── 402.blade.php
│ ├── 403.blade.php
│ ├── 404.blade.php
│ ├── 419.blade.php
│ ├── 429.blade.php
│ ├── 500.blade.php
│ ├── 503.blade.php
│ ├── layout.blade.php
│ └── minimal.blade.php
│ ├── items
│ ├── form.blade.php
│ └── show.blade.php
│ ├── layouts
│ ├── app.blade.php
│ └── guest.blade.php
│ ├── pages
│ └── changelog.blade.php
│ ├── profile
│ ├── edit.blade.php
│ └── partials
│ │ ├── delete-user-form.blade.php
│ │ ├── update-password-form.blade.php
│ │ └── update-profile-information-form.blade.php
│ └── projects
│ ├── form.blade.php
│ ├── index.blade.php
│ └── show.blade.php
├── routes
├── auth.php
├── console.php
└── web.php
├── storage
├── app
│ ├── .gitignore
│ ├── private
│ │ └── .gitignore
│ └── public
│ │ └── .gitignore
├── framework
│ ├── .gitignore
│ ├── cache
│ │ ├── .gitignore
│ │ └── data
│ │ │ └── .gitignore
│ ├── sessions
│ │ └── .gitignore
│ ├── testing
│ │ └── .gitignore
│ └── views
│ │ └── .gitignore
└── logs
│ └── .gitignore
├── tailwind.config.js
├── tests
├── Feature
│ ├── Auth
│ │ ├── AuthenticationTest.php
│ │ ├── EmailVerificationTest.php
│ │ ├── PasswordConfirmationTest.php
│ │ ├── PasswordResetTest.php
│ │ ├── PasswordUpdateTest.php
│ │ └── RegistrationTest.php
│ ├── ExampleTest.php
│ └── ProfileTest.php
├── Pest.php
├── TestCase.php
└── Unit
│ └── ExampleTest.php
└── vite.config.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_size = 4
7 | indent_style = space
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
14 | [*.{yml,yaml}]
15 | indent_size = 2
16 |
17 | [docker-compose.yml]
18 | indent_size = 4
19 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | APP_NAME=infiniteloop
2 | APP_ENV=local
3 | APP_KEY=base64:v7bkmKOwXlSAmzDhJx3ecmN3yT+IStJL+yrs8aYbBas=
4 | APP_DEBUG=true
5 | APP_TIMEZONE='Europe/Berlin'
6 | APP_URL=https://infiniteloop.test
7 | VITE_APP_NAME="${APP_NAME}"
8 |
9 | APP_LOCALE=en
10 | APP_FALLBACK_LOCALE=en
11 | APP_FAKER_LOCALE=en_EN
12 |
13 | OPENAI_API_KEY=''
14 | OPENAI_ORGANIZATION=''
15 |
16 | APP_MAINTENANCE_DRIVER=file
17 | # APP_MAINTENANCE_STORE=database
18 |
19 | ALLOWED_REGISTRATION_DOMAIN='@domain.tld'
20 |
21 | PHP_CLI_SERVER_WORKERS=4
22 |
23 | BCRYPT_ROUNDS=12
24 |
25 | LOG_CHANNEL=stack
26 | LOG_STACK=single
27 | LOG_DEPRECATIONS_CHANNEL=null
28 | LOG_LEVEL=debug
29 |
30 | DB_CONNECTION=sqlite
31 | # DB_HOST=127.0.0.1
32 | # DB_PORT=3306
33 | # DB_DATABASE=laravel
34 | # DB_USERNAME=root
35 | # DB_PASSWORD=
36 |
37 | SESSION_DRIVER=database
38 | SESSION_LIFETIME=120
39 | SESSION_ENCRYPT=false
40 | SESSION_PATH=/
41 | SESSION_DOMAIN=null
42 |
43 | BROADCAST_CONNECTION=log
44 | FILESYSTEM_DISK=local
45 | QUEUE_CONNECTION=database
46 |
47 | CACHE_STORE=database
48 | CACHE_PREFIX=
49 |
50 | MEMCACHED_HOST=127.0.0.1
51 |
52 | MAIL_MAILER=log
53 | MAIL_SCHEME=null
54 | MAIL_HOST=127.0.0.1
55 | MAIL_PORT=2525
56 | MAIL_USERNAME=null
57 | MAIL_PASSWORD=null
58 | MAIL_FROM_ADDRESS="hello@example.com"
59 | MAIL_FROM_NAME="${APP_NAME}"
60 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 |
3 | *.blade.php diff=html
4 | *.css diff=css
5 | *.html diff=html
6 | *.md diff=markdown
7 | *.php diff=php
8 |
9 | /.github export-ignore
10 | CHANGELOG.md export-ignore
11 | .styleci.yml export-ignore
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.phpunit.cache
2 | /node_modules
3 | /public/build
4 | /public/hot
5 | /public/storage
6 | /storage/*.key
7 | /storage/pail
8 | /vendor
9 | .env
10 | .env.backup
11 | .env.production
12 | .phpactor.json
13 | .phpunit.result.cache
14 | Homestead.json
15 | Homestead.yaml
16 | npm-debug.log
17 | yarn-error.log
18 | /auth.json
19 | /.fleet
20 | /.idea
21 | /.nova
22 | /.vscode
23 | /.zed
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # infiniteloop
2 |
3 | Use this app to gather UserStories from inside your company. Let users upvote ideas and categories ideas by type and prio.
4 |
5 | 
6 |
7 | ## What's inside?
8 |
9 | - Create, Show, Update and Delete Projects
10 | - Configure a base User Story for every project
11 | - CRUD User Stories
12 | - Set User Story type and priority
13 | - Restrict access to @domain.tld users in .env
14 | - Use /ai in comment to let chatGPT directly change/enrich User Story
15 |
16 | ## Registration Domain Control
17 |
18 | Define your companies Email domain in your `.env` file to prevent external users to register to the platform.
19 |
20 | ## Installation
21 |
22 | Checkout the repository and rename the `.env.example` to `.env`.
23 |
24 | - Run `composer install`
25 | - Run `npm install`
26 | - Run `php artisan migrate` or `php artisan migrate:fresh --seed`
27 |
28 | Missing parameters are marked with `*****`
29 |
30 | ### Naming Conventions
31 |
32 | - Use camelCase for naming throughout the Laravel project.
33 | - In `resources/views/{folders}`, use plural names when working with models like `Companies` or `Orders`. For example, the Media Library should remain singular as it represents a collection of media.
34 | - Route names should also be plural for resources, e.g., `companies.index` or `companies.show`. For the company selector page, `company.select` is used.
35 | - Admin routes use the `admin.` prefix.
36 |
37 | ### Change Documentation
38 |
39 | All contributors are required to document their changes in the `CHANGELOG.md` file. Each entry should include:
40 | - The version number being updated.
41 | - A brief description of the change (e.g., added features, bug fixes, or breaking changes).
42 | - The author or contributor's name (optional).
43 |
44 | This ensures transparency and provides a clear history of the project's evolution. For guidance, follow the existing structure in the `CHANGELOG.md` file.
45 |
46 | ## Security Vulnerabilities
47 |
48 | If you discover any security vulnerabilities within adfinity, feel free to PR or drop an email to infiniteloop@0x25.de.
49 |
50 | ## License
51 |
52 | infiniteloop is open-source :-)
53 |
--------------------------------------------------------------------------------
/Screenshots/Showcase-1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1aWebmarketing/infiniteloop/a9e316b86b51fbe111f53a84f783792614918fe9/Screenshots/Showcase-1.gif
--------------------------------------------------------------------------------
/Screenshots/screen-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1aWebmarketing/infiniteloop/a9e316b86b51fbe111f53a84f783792614918fe9/Screenshots/screen-1.png
--------------------------------------------------------------------------------
/app/Console/Commands/GenerateUUIDs.php:
--------------------------------------------------------------------------------
1 | each(function ($item) {
31 | $item->uuid = Str::uuid();
32 | $item->save();
33 | });
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/Helpers/general.php:
--------------------------------------------------------------------------------
1 | timezone(config('app.timezone'))->format('d.m.Y H:i');
11 | }
12 |
13 | function canUpvote(User $user, Item $item) : bool
14 | {
15 | return ItemUpvoteService::canUpvote($user, $item);
16 | }
17 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Admin/AdminController.php:
--------------------------------------------------------------------------------
1 | user()->github_token)
13 | ->get('https://api.github.com/user/repos')->json();
14 |
15 | $repos = [];
16 | foreach ($response as $repo) {
17 | if('.github' === $repo['name']) continue;
18 |
19 | $repos[$repo['id']] = array(
20 | 'name' => $repo['name'],
21 | 'owner' => $repo['owner']['login'],
22 | );
23 | }
24 |
25 | return view('admin.index', [
26 | 'repos' => $repos,
27 | ]);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Admin/GitHubController.php:
--------------------------------------------------------------------------------
1 | scopes(['repo', 'admin:org'])
14 | ->redirect();
15 | }
16 |
17 | public function store()
18 | {
19 | $githubUser = Socialite::driver('github')->user();
20 |
21 | $admin = auth()->user();
22 | $admin->github_token = $githubUser->token;
23 | $admin->save();
24 |
25 | return redirect('/admin')->with('success', 'GitHub connected!');
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/AuthenticatedSessionController.php:
--------------------------------------------------------------------------------
1 | authenticate();
28 |
29 | $request->session()->regenerate();
30 |
31 | return redirect()->intended(route('dashboard', absolute: false));
32 | }
33 |
34 | /**
35 | * Destroy an authenticated session.
36 | */
37 | public function destroy(Request $request): RedirectResponse
38 | {
39 | Auth::guard('web')->logout();
40 |
41 | $request->session()->invalidate();
42 |
43 | $request->session()->regenerateToken();
44 |
45 | return redirect('/');
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/ConfirmablePasswordController.php:
--------------------------------------------------------------------------------
1 | validate([
28 | 'email' => $request->user()->email,
29 | 'password' => $request->password,
30 | ])) {
31 | throw ValidationException::withMessages([
32 | 'password' => __('auth.password'),
33 | ]);
34 | }
35 |
36 | $request->session()->put('auth.password_confirmed_at', time());
37 |
38 | return redirect()->intended(route('dashboard', absolute: false));
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/EmailVerificationNotificationController.php:
--------------------------------------------------------------------------------
1 | user()->hasVerifiedEmail()) {
17 | return redirect()->intended(route('dashboard', absolute: false));
18 | }
19 |
20 | $request->user()->sendEmailVerificationNotification();
21 |
22 | return back()->with('status', 'verification-link-sent');
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/EmailVerificationPromptController.php:
--------------------------------------------------------------------------------
1 | user()->hasVerifiedEmail()
18 | ? redirect()->intended(route('dashboard', absolute: false))
19 | : view('auth.verify-email');
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/PasswordController.php:
--------------------------------------------------------------------------------
1 | validateWithBag('updatePassword', [
19 | 'current_password' => ['required', 'current_password'],
20 | 'password' => ['required', Password::defaults(), 'confirmed'],
21 | ]);
22 |
23 | $request->user()->update([
24 | 'password' => Hash::make($validated['password']),
25 | ]);
26 |
27 | return back()->with('status', 'password-updated');
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/PasswordResetLinkController.php:
--------------------------------------------------------------------------------
1 | validate([
29 | 'email' => ['required', 'email'],
30 | ]);
31 |
32 | // We will send the password reset link to this user. Once we have attempted
33 | // to send the link, we will examine the response then see the message we
34 | // need to show to the user. Finally, we'll send out a proper response.
35 | $status = Password::sendResetLink(
36 | $request->only('email')
37 | );
38 |
39 | return $status == Password::RESET_LINK_SENT
40 | ? back()->with('status', __($status))
41 | : back()->withInput($request->only('email'))
42 | ->withErrors(['email' => __($status)]);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/RegisteredUserController.php:
--------------------------------------------------------------------------------
1 | validate([
35 | 'name' => ['required', 'string', 'max:255'],
36 | 'email' => [
37 | 'required',
38 | 'string',
39 | 'lowercase',
40 | 'email',
41 | 'max:255',
42 | 'unique:' . User::class,
43 | function ($attribute, $value, $fail) use ($allowedDomain) {
44 | if (!str_ends_with($value, $allowedDomain)) {
45 | $fail("The email must belong to the allowed domain: $allowedDomain.");
46 | }
47 | },
48 | ],
49 | 'password' => ['required', 'confirmed', Rules\Password::defaults()],
50 | ]);
51 |
52 | $user = User::create([
53 | 'name' => $request->name,
54 | 'email' => $request->email,
55 | 'password' => Hash::make($request->password),
56 | ]);
57 |
58 | event(new Registered($user));
59 |
60 | Auth::login($user);
61 |
62 | return redirect(route('dashboard', absolute: false));
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/VerifyEmailController.php:
--------------------------------------------------------------------------------
1 | user()->hasVerifiedEmail()) {
18 | return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
19 | }
20 |
21 | if ($request->user()->markEmailAsVerified()) {
22 | event(new Verified($request->user()));
23 | }
24 |
25 | return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/Http/Controllers/CommentController.php:
--------------------------------------------------------------------------------
1 | validate([
16 | 'text' => 'required|string',
17 | ]);
18 |
19 | $fields['item_id'] = $item->id;
20 | $fields['user_id'] = auth()->id();
21 |
22 | $comment = Comment::create($fields);
23 |
24 | SendNewCommentNotification::dispatch($item, $comment);
25 |
26 | if(!str_contains($fields['text'], '/ki') === false){
27 | $fields['text'] = str_replace('/ki', '', $fields['text']);
28 | ChatGPTService::optimizeItem($item, $fields['text']);
29 | }
30 |
31 | return redirect()
32 | ->route('items.show', [
33 | 'project' => $item->project->id,
34 | 'item' => $item->uuid
35 | ]);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Controller.php:
--------------------------------------------------------------------------------
1 | validate([
17 | 'file' => 'required|mimes:jpeg,jpg,png,mp4,mov,webp,webm',
18 | ]);
19 |
20 | if ($request->file('file')->getSize() > 100_000_000) {
21 | return response()->json([
22 | 'success' => false,
23 | 'error' => 'File is bigger than 100Mb',
24 | ], 400);
25 | }
26 |
27 | $file = $request->file('file');
28 |
29 | // Determine file type
30 | $type = match (strtolower($file->getClientOriginalExtension())) {
31 | 'mp4', 'mov', 'webm' => 'VIDEO',
32 | default => 'IMAGE',
33 | };
34 |
35 | // Generate a unique filename
36 | $randomFileName = Str::uuid() . '.' . $file->getClientOriginalExtension();
37 |
38 | $path = $file->storeAs("creatives", $randomFileName, 'public');
39 |
40 | Creative::create([
41 | 'item_id' => $item->id,
42 | 'name' => $file->getClientOriginalName(),
43 | 'type' => $type,
44 | 'path' => $path,
45 | ]);
46 |
47 | return response()->json([
48 | 'message' => 'File uploaded successfully.',
49 | 'type' => $type,
50 | 'path' => Storage::url($path),
51 | ]);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/Http/Controllers/PagesController.php:
--------------------------------------------------------------------------------
1 | $request->user(),
21 | ]);
22 | }
23 |
24 | /**
25 | * Update the user's profile information.
26 | */
27 | public function update(ProfileUpdateRequest $request): RedirectResponse
28 | {
29 | $request->user()->fill($request->validated());
30 |
31 | if ($request->user()->isDirty('email')) {
32 | $request->user()->email_verified_at = null;
33 | }
34 |
35 | $request->user()->save();
36 |
37 | return Redirect::route('profile.edit')->with('status', 'profile-updated');
38 | }
39 |
40 | /**
41 | * Delete the user's account.
42 | */
43 | public function destroy(Request $request): RedirectResponse
44 | {
45 | $request->validateWithBag('userDeletion', [
46 | 'password' => ['required', 'current_password'],
47 | ]);
48 |
49 | $user = $request->user();
50 |
51 | Auth::logout();
52 |
53 | $user->delete();
54 |
55 | $request->session()->invalidate();
56 | $request->session()->regenerateToken();
57 |
58 | return Redirect::to('/');
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/app/Http/Middleware/Admin.php:
--------------------------------------------------------------------------------
1 | user()->is_admin === 1)
19 | {
20 | return $next($request);
21 | }
22 |
23 | abort(403);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/Http/Requests/ProfileUpdateRequest.php:
--------------------------------------------------------------------------------
1 | |string>
15 | */
16 | public function rules(): array
17 | {
18 | return [
19 | 'name' => ['required', 'string', 'max:255'],
20 | 'email' => [
21 | 'required',
22 | 'string',
23 | 'lowercase',
24 | 'email',
25 | 'max:255',
26 | Rule::unique(User::class)->ignore($this->user()->id),
27 | ],
28 | ];
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/app/Jobs/ItemNewStatusNotification.php:
--------------------------------------------------------------------------------
1 | item->user->email => 1,
34 | );
35 |
36 | foreach($this->item->comments as $comment) {
37 | $receivers[$comment->user->email] = 1;
38 | }
39 |
40 | foreach($receivers as $email => $notify) {
41 | Mail::to($email)->send(new ItemNewStatus($this->item, $this->oldStatus, $this->newStatus));
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/Jobs/SendNewCommentNotification.php:
--------------------------------------------------------------------------------
1 | item->user->email => 1,
34 | );
35 |
36 | foreach($this->item->comments as $comment) {
37 | $receivers[$comment->user->email] = 1;
38 | }
39 |
40 | unset($receivers[$this->comment->user->email]);
41 |
42 | // print_r($receivers);
43 |
44 | foreach($receivers as $email => $notify) {
45 | Mail::to($email)->send(new NewItemComment($this->item, $this->comment));
46 | }
47 |
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/Livewire/ItemStatusSelector.php:
--------------------------------------------------------------------------------
1 | item = $item;
17 | $this->status = $this->item->status;
18 | }
19 |
20 | public function updatedStatus()
21 | {
22 | $comment = $this->item->comments()->create([
23 | 'user_id' => auth()->id(),
24 | 'text' => $this->item->status . ' -> ' . $this->status,
25 | ]);
26 |
27 | ItemNewStatusNotification::dispatch(
28 | $this->item,
29 | $this->item->status,
30 | $this->status
31 | );
32 |
33 | $this->item->update([
34 | 'status' => $this->status
35 | ]);
36 |
37 | }
38 |
39 | public function render()
40 | {
41 | return <<<'HTML'
42 |
43 |
44 |
45 | HTML;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/Mail/ItemNewStatus.php:
--------------------------------------------------------------------------------
1 | item->title,
36 | );
37 | }
38 |
39 | /**
40 | * Get the message content definition.
41 | */
42 | public function content(): Content
43 | {
44 | return new Content(
45 | view: 'emails.item-new-status',
46 | with: [
47 | 'item_name' => $this->item->title,
48 | 'old_status' => $this->oldStatus,
49 | 'new_status' => $this->newStatus,
50 | 'item_url' => route('items.show', ['project' => $this->item->project, 'item' => $this->item->uuid]),
51 | ]
52 | );
53 | }
54 |
55 | /**
56 | * Get the attachments for the message.
57 | *
58 | * @return array
59 | */
60 | public function attachments(): array
61 | {
62 | return [];
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/app/Mail/NewItemComment.php:
--------------------------------------------------------------------------------
1 | item->title,
36 | );
37 | }
38 |
39 | /**
40 | * Get the message content definition.
41 | */
42 | public function content(): Content
43 | {
44 | return new Content(
45 | view: 'emails.item-new-comment',
46 | with: [
47 | 'item_name' => $this->item->title,
48 | 'comment_text' => $this->comment->text,
49 | 'author_name' => $this->comment->user->name,
50 | 'item_url' => route('items.show', ['project' => $this->item->project, 'item' => $this->item->uuid]),
51 | ]
52 | );
53 | }
54 |
55 | /**
56 | * Get the attachments for the message.
57 | *
58 | * @return array
59 | */
60 | public function attachments(): array
61 | {
62 | return [];
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/app/Models/Comment.php:
--------------------------------------------------------------------------------
1 | belongsTo(User::class);
18 | }
19 |
20 | public function item()
21 | {
22 | return $this->belongsTo(Item::class);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/Models/Creative.php:
--------------------------------------------------------------------------------
1 | belongsTo(Item::class);
23 | }
24 |
25 | public function display(int $maxHeight = 300)
26 | {
27 | if($this->type === 'IMAGE')
28 | {
29 | return " path) . "'
30 | class='object-cover hover:object-contain hover:outline outline-gray-300 hover:cursor-pointer'
31 | style='max-height: {{$maxHeight}}px; aspect-ratio: 1/1;'>";
32 | }
33 | if($this->type === 'VIDEO')
34 | {
35 | return "
39 | path) . "'>
40 | ";
41 |
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/Models/Destination.php:
--------------------------------------------------------------------------------
1 | belongsTo(Item::class);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/Models/Project.php:
--------------------------------------------------------------------------------
1 | logo )
20 | {
21 | return asset('storage/' . $this->logo);
22 | }
23 | return asset('images/project-default.png');
24 | }
25 |
26 | public function items()
27 | {
28 | return $this->hasMany(Item::class);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/app/Models/User.php:
--------------------------------------------------------------------------------
1 | */
13 | use HasFactory, Notifiable;
14 |
15 | /**
16 | * The attributes that are mass assignable.
17 | *
18 | * @var list
19 | */
20 | protected $fillable = [
21 | 'name',
22 | 'email',
23 | 'password',
24 | 'github_token',
25 | ];
26 |
27 | /**
28 | * The attributes that should be hidden for serialization.
29 | *
30 | * @var list
31 | */
32 | protected $hidden = [
33 | 'password',
34 | 'remember_token',
35 | ];
36 |
37 | /**
38 | * Get the attributes that should be cast.
39 | *
40 | * @return array
41 | */
42 | protected function casts(): array
43 | {
44 | return [
45 | 'email_verified_at' => 'datetime',
46 | 'password' => 'hashed',
47 | ];
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/Policies/ItemPolicy.php:
--------------------------------------------------------------------------------
1 | check();
14 | }
15 |
16 | /**
17 | * Determine whether the user can update the model.
18 | */
19 | public function update(User $user, Item $item): bool
20 | {
21 | return $user->id === $item->user_id || $user->is_admin === 1;
22 | }
23 |
24 | /**
25 | * Determine whether the user can delete the model.
26 | */
27 | public function delete(User $user, Item $item): bool
28 | {
29 | return $user->id === $item->user_id || $user->is_admin === 1;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/Providers/AppServiceProvider.php:
--------------------------------------------------------------------------------
1 | user()->is_admin === 1;
28 | });
29 | Gate::define('edit-project', function(User $user, Project $project){
30 | return auth()->user()->is_admin === 1 || $project->user_id == auth()->id();
31 | });
32 | Gate::define('edit-item', function(User $user, Item $item){
33 | return auth()->user()->is_admin === 1 || $item->user_id == auth()->id();
34 | });
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/Services/ChatGPTService.php:
--------------------------------------------------------------------------------
1 | $title,
19 | 'story' => $story,
20 | ];
21 | }
22 |
23 | static public function optimizeItem(Item $item, string $comment): void
24 | {
25 | $result = OpenAI::chat()->create([
26 | 'model' => 'gpt-3.5-turbo',
27 | 'messages' => [
28 | ['role' => 'user', 'content' => "Optimiere folgende vorhandene UserStory anhand eines Kommentars. Gib mir nur das Markdown zurück ohne Einleitungstext und Abschlusstext."],
29 | ['role' => 'user', 'content' => "Die UserStory: " . $item->story],
30 | ['role' => 'user', 'content' => "Das Kommentar: " . $comment],
31 | ],
32 | ]);
33 |
34 | $item->story = $result->choices[0]->message->content;
35 | $item->translated = self::translateAndMarkdown($item->title, $item->story);
36 | $item->save();
37 | }
38 |
39 | static private function prompt(string $prompt) :string
40 | {
41 | $result = OpenAI::chat()->create([
42 | 'model' => 'gpt-3.5-turbo',
43 | 'messages' => [
44 | ['role' => 'user', 'content' => $prompt],
45 | ],
46 | ]);
47 | \Log::info(print_r($result, 1));
48 | return $result->choices[0]->message->content;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/Services/ItemUpvoteService.php:
--------------------------------------------------------------------------------
1 | id . '-item-' . $item->id) )
14 | {
15 | return false;
16 | }
17 |
18 | return true;
19 | }
20 |
21 | public static function upvote(User $user, Item $item)
22 | {
23 | $item->increment('voting');
24 | Cache::put('user-' . $user->id . '-item-' . $item->id, 1);
25 | }
26 |
27 | public static function downvote(User $user, Item $item)
28 | {
29 | $item->decrement('voting');
30 | Cache::pull('user-' . $user->id . '-item-' . $item->id, 1);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/Services/QueueWorker.php:
--------------------------------------------------------------------------------
1 | handleCommand(new ArgvInput);
14 |
15 | exit($status);
16 |
--------------------------------------------------------------------------------
/bootstrap/app.php:
--------------------------------------------------------------------------------
1 | withRouting(
10 | web: __DIR__.'/../routes/web.php',
11 | commands: __DIR__.'/../routes/console.php',
12 | health: '/up',
13 | )
14 | ->withMiddleware(function (Middleware $middleware) {
15 | $middleware->alias([
16 | 'admin' => Admin::class,
17 | ]);
18 | })
19 | ->withExceptions(function (Exceptions $exceptions) {
20 | //
21 | })->create();
22 |
--------------------------------------------------------------------------------
/bootstrap/cache/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/bootstrap/providers.php:
--------------------------------------------------------------------------------
1 | env('OPENAI_API_KEY'),
16 | 'organization' => env('OPENAI_ORGANIZATION'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Request Timeout
21 | |--------------------------------------------------------------------------
22 | |
23 | | The timeout may be used to specify the maximum number of seconds to wait
24 | | for a response. By default, the client will time out after 30 seconds.
25 | */
26 |
27 | 'request_timeout' => env('OPENAI_REQUEST_TIMEOUT', 30),
28 | ];
29 |
--------------------------------------------------------------------------------
/config/services.php:
--------------------------------------------------------------------------------
1 | [
17 | 'allowed_domain' => env('ALLOWED_REGISTRATION_DOMAIN')
18 | ],
19 |
20 | 'postmark' => [
21 | 'token' => env('POSTMARK_TOKEN'),
22 | ],
23 |
24 | 'ses' => [
25 | 'key' => env('AWS_ACCESS_KEY_ID'),
26 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
27 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
28 | ],
29 |
30 | 'resend' => [
31 | 'key' => env('RESEND_KEY'),
32 | ],
33 |
34 | 'slack' => [
35 | 'notifications' => [
36 | 'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'),
37 | 'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'),
38 | ],
39 | ],
40 |
41 | 'github' => [
42 | 'client_id' => env('GITHUB_CLIENT_ID'),
43 | 'client_secret' => env('GITHUB_CLIENT_SECRET'),
44 | 'redirect' => env('GITHUB_REDIRECT_URI'),
45 | ],
46 |
47 | ];
48 |
--------------------------------------------------------------------------------
/database/.gitignore:
--------------------------------------------------------------------------------
1 | *.sqlite*
2 |
--------------------------------------------------------------------------------
/database/factories/UserFactory.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | class UserFactory extends Factory
13 | {
14 | /**
15 | * The current password being used by the factory.
16 | */
17 | protected static ?string $password;
18 |
19 | /**
20 | * Define the model's default state.
21 | *
22 | * @return array
23 | */
24 | public function definition(): array
25 | {
26 | return [
27 | 'name' => fake()->name(),
28 | 'email' => fake()->unique()->safeEmail(),
29 | 'email_verified_at' => now(),
30 | 'password' => static::$password ??= Hash::make('password'),
31 | 'remember_token' => Str::random(10),
32 | ];
33 | }
34 |
35 | /**
36 | * Indicate that the model's email address should be unverified.
37 | */
38 | public function unverified(): static
39 | {
40 | return $this->state(fn (array $attributes) => [
41 | 'email_verified_at' => null,
42 | ]);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/database/migrations/0001_01_01_000000_create_users_table.php:
--------------------------------------------------------------------------------
1 | id();
16 | $table->string('name');
17 | $table->string('email')->unique();
18 | $table->timestamp('email_verified_at')->nullable();
19 | $table->string('password');
20 | $table->boolean('is_admin')->default(0);
21 | $table->rememberToken();
22 | $table->timestamps();
23 | });
24 |
25 | Schema::create('password_reset_tokens', function (Blueprint $table) {
26 | $table->string('email')->primary();
27 | $table->string('token');
28 | $table->timestamp('created_at')->nullable();
29 | });
30 |
31 | Schema::create('sessions', function (Blueprint $table) {
32 | $table->string('id')->primary();
33 | $table->foreignId('user_id')->nullable()->index();
34 | $table->string('ip_address', 45)->nullable();
35 | $table->text('user_agent')->nullable();
36 | $table->longText('payload');
37 | $table->integer('last_activity')->index();
38 | });
39 | }
40 |
41 | /**
42 | * Reverse the migrations.
43 | */
44 | public function down(): void
45 | {
46 | Schema::dropIfExists('users');
47 | Schema::dropIfExists('password_reset_tokens');
48 | Schema::dropIfExists('sessions');
49 | }
50 | };
51 |
--------------------------------------------------------------------------------
/database/migrations/0001_01_01_000001_create_cache_table.php:
--------------------------------------------------------------------------------
1 | string('key')->primary();
16 | $table->mediumText('value');
17 | $table->integer('expiration');
18 | });
19 |
20 | Schema::create('cache_locks', function (Blueprint $table) {
21 | $table->string('key')->primary();
22 | $table->string('owner');
23 | $table->integer('expiration');
24 | });
25 | }
26 |
27 | /**
28 | * Reverse the migrations.
29 | */
30 | public function down(): void
31 | {
32 | Schema::dropIfExists('cache');
33 | Schema::dropIfExists('cache_locks');
34 | }
35 | };
36 |
--------------------------------------------------------------------------------
/database/migrations/0001_01_01_000002_create_jobs_table.php:
--------------------------------------------------------------------------------
1 | id();
16 | $table->string('queue')->index();
17 | $table->longText('payload');
18 | $table->unsignedTinyInteger('attempts');
19 | $table->unsignedInteger('reserved_at')->nullable();
20 | $table->unsignedInteger('available_at');
21 | $table->unsignedInteger('created_at');
22 | });
23 |
24 | Schema::create('job_batches', function (Blueprint $table) {
25 | $table->string('id')->primary();
26 | $table->string('name');
27 | $table->integer('total_jobs');
28 | $table->integer('pending_jobs');
29 | $table->integer('failed_jobs');
30 | $table->longText('failed_job_ids');
31 | $table->mediumText('options')->nullable();
32 | $table->integer('cancelled_at')->nullable();
33 | $table->integer('created_at');
34 | $table->integer('finished_at')->nullable();
35 | });
36 |
37 | Schema::create('failed_jobs', function (Blueprint $table) {
38 | $table->id();
39 | $table->string('uuid')->unique();
40 | $table->text('connection');
41 | $table->text('queue');
42 | $table->longText('payload');
43 | $table->longText('exception');
44 | $table->timestamp('failed_at')->useCurrent();
45 | });
46 | }
47 |
48 | /**
49 | * Reverse the migrations.
50 | */
51 | public function down(): void
52 | {
53 | Schema::dropIfExists('jobs');
54 | Schema::dropIfExists('job_batches');
55 | Schema::dropIfExists('failed_jobs');
56 | }
57 | };
58 |
--------------------------------------------------------------------------------
/database/migrations/2025_01_28_155420_create_projects_table.php:
--------------------------------------------------------------------------------
1 | id();
16 | $table->timestamps();
17 |
18 | $table->foreignId('user_id');
19 |
20 | $table->string('logo')->nullable();
21 | $table->string('name');
22 | $table->text('description');
23 | });
24 | }
25 |
26 | /**
27 | * Reverse the migrations.
28 | */
29 | public function down(): void
30 | {
31 | Schema::dropIfExists('projects');
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/database/migrations/2025_01_28_155641_create_items_table.php:
--------------------------------------------------------------------------------
1 | id();
16 | $table->timestamps();
17 |
18 | $table->foreignId('project_id')->constrained()->onDelete('cascade');
19 | $table->foreignId('user_id');
20 |
21 | $table->string('title');
22 | $table->text('story');
23 | $table->string('status')->default('CREATED');
24 | $table->string('priority')->default('LOW');
25 | $table->string('type')->default('TASK');
26 | $table->integer('voting')->default(0);
27 | });
28 | }
29 |
30 | /**
31 | * Reverse the migrations.
32 | */
33 | public function down(): void
34 | {
35 | Schema::dropIfExists('items');
36 | }
37 | };
38 |
--------------------------------------------------------------------------------
/database/migrations/2025_01_28_193840_add_template_to_projects_table.php:
--------------------------------------------------------------------------------
1 | text('template')->default('Was wünscht du dir? ');
16 | });
17 | }
18 |
19 | /**
20 | * Reverse the migrations.
21 | */
22 | public function down(): void
23 | {
24 | Schema::table('projects', function (Blueprint $table) {
25 | //
26 | });
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/database/migrations/2025_01_28_215636_create_comments_table.php:
--------------------------------------------------------------------------------
1 | id();
16 | $table->timestamps();
17 |
18 | $table->foreignId('user_id');
19 | $table->foreignId('item_id')->constrained()->onDelete('cascade');
20 | $table->text('text');
21 | });
22 | }
23 |
24 | /**
25 | * Reverse the migrations.
26 | */
27 | public function down(): void
28 | {
29 | Schema::dropIfExists('comments');
30 | }
31 | };
32 |
--------------------------------------------------------------------------------
/database/migrations/2025_02_06_223953_add_translated_column_to_items_table.php:
--------------------------------------------------------------------------------
1 | text('translated')->nullable()->after('story');
16 | });
17 | }
18 |
19 | /**
20 | * Reverse the migrations.
21 | */
22 | public function down(): void
23 | {
24 | Schema::table('items', function (Blueprint $table) {
25 | $table->dropColumn('translated');
26 | });
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/database/migrations/2025_02_12_092747_change_column_in_items_table.php:
--------------------------------------------------------------------------------
1 | json('translated')->nullable()->after('story');
16 | });
17 | }
18 |
19 | /**
20 | * Reverse the migrations.
21 | */
22 | public function down(): void
23 | {
24 | Schema::table('items', function (Blueprint $table) {
25 | $table->text('translated')->nullable()->after('story');
26 | });
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/database/migrations/2025_02_14_200600_add_uuid_to_items_table.php:
--------------------------------------------------------------------------------
1 | uuid('uuid')->after('id')->nullable();
16 | });
17 | }
18 |
19 | /**
20 | * Reverse the migrations.
21 | */
22 | public function down(): void
23 | {
24 | Schema::table('items', function (Blueprint $table) {
25 | //
26 | });
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/database/migrations/2025_02_22_111529_create_destinations_table.php:
--------------------------------------------------------------------------------
1 | id();
16 | $table->timestamps();
17 |
18 | $table->foreignId('item_id')->constrained('items')->onDelete('cascade');
19 | $table->string('type'); // GITHUB, TASKIFY, Whatever
20 | $table->string('remote_id');
21 | });
22 | }
23 |
24 | /**
25 | * Reverse the migrations.
26 | */
27 | public function down(): void
28 | {
29 | Schema::dropIfExists('destinations');
30 | }
31 | };
32 |
--------------------------------------------------------------------------------
/database/migrations/2025_03_08_212040_create_creatives_table.php:
--------------------------------------------------------------------------------
1 | uuid('id');
16 | $table->timestamps();
17 | $table->foreignUuid('item_id');
18 | $table->string('name');
19 | $table->string('path');
20 | $table->string('type');
21 | });
22 | }
23 |
24 | /**
25 | * Reverse the migrations.
26 | */
27 | public function down(): void
28 | {
29 | Schema::dropIfExists('creatives');
30 | }
31 | };
32 |
--------------------------------------------------------------------------------
/database/migrations/2025_03_20_170310_add_github_to_users_table.php:
--------------------------------------------------------------------------------
1 | string('github_token')->nullable();
16 | });
17 | }
18 |
19 | /**
20 | * Reverse the migrations.
21 | */
22 | public function down(): void
23 | {
24 | Schema::table('users', function (Blueprint $table) {
25 | $table->dropColumn('github_token');
26 | });
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/lang/de/items.php:
--------------------------------------------------------------------------------
1 | 'Story anlegen',
5 | 'edit' => 'Story bearbeiten',
6 |
7 | 'in_progress' => 'In Arbeit',
8 | 'in_progress_abbr' => 'A',
9 | 'done' => 'Fertig',
10 | 'done_abbr' => 'F',
11 | 'open' => 'Offen',
12 | 'open_abbr' => 'O',
13 |
14 | 'markdown' => 'Markdown',
15 | 'comment' => 'Kommentieren',
16 | 'send_comment' => 'Kommentar absenden',
17 |
18 | 'low' => 'Niedrig',
19 | 'low_description' => 'Schönheits/Stylingfehler welche die Funktion des Systems nicht beeinflussen.',
20 | 'medium' => 'Mittel',
21 | 'medium_description' => 'Funktionen die für zukünftige Kunden oder Projektbenutzer wichtig werden können.',
22 | 'high' => 'Hoch',
23 | 'high_description' => 'Sollte als erstes von den normalen ToDos bearbeitet werden da die Funktion schnellstmöglich gebraucht wird.',
24 | 'critical' => 'Kritisch',
25 | 'critical_description' => 'Das System funktioniert nicht oder verhält sich in wichtigen Situationen komplett falsch.',
26 |
27 | 'bug' => 'Bug',
28 | 'bug_description' => 'Eine fehlerhafte Funktion im Projekt',
29 | 'feature' => 'Feature',
30 | 'feature_description' => 'Eine neue Funktion für das Projekt',
31 | 'task' => 'Aufgabe',
32 | 'task_description' => 'Generelle Aufgabe für das System die weder einen Fehler löst noch ein neues Feature hinzufügt.',
33 | ];
34 |
--------------------------------------------------------------------------------
/lang/de/pagination.php:
--------------------------------------------------------------------------------
1 | '« Zurück',
17 | 'next' => 'Weiter »',
18 |
19 | ];
20 |
--------------------------------------------------------------------------------
/lang/de/passwords.php:
--------------------------------------------------------------------------------
1 | 'Dein Passwort wurde zurückgesetzt.',
18 | 'sent' => 'Wir haben dir den Link zum Zurücksetzen deines Passworts per E-Mail gesendet.',
19 | 'throttled' => 'Bitte warte, bevor du es erneut versuchst.',
20 | 'token' => 'Dieses Token zum Zurücksetzen des Passworts ist ungültig.',
21 | 'user' => "Wir können keinen Benutzer mit dieser E-Mail-Adresse finden.",
22 |
23 | ];
24 |
--------------------------------------------------------------------------------
/lang/de/profile.php:
--------------------------------------------------------------------------------
1 | 'Profil',
5 | ];
6 |
--------------------------------------------------------------------------------
/lang/de/projects.php:
--------------------------------------------------------------------------------
1 | 'Projektübersicht',
5 | 'show' => 'Projekt ansehen',
6 | 'create' => 'Projekt anlegen',
7 |
8 | 'logo' => 'Logo',
9 | 'description' => 'Beschreibung',
10 | 'story_format' => 'Story Format',
11 | 'save' => 'Speichern',
12 |
13 | 'back_to_overview' => 'Zurück zur Projektübersicht',
14 | 'create_story' => '✨ User Story erstellen ✨',
15 |
16 | 'back_to_project' => 'Zurück zu :project',
17 | ];
18 |
--------------------------------------------------------------------------------
/lang/en/auth.php:
--------------------------------------------------------------------------------
1 | "The credentials are incorrect.",
18 | "throttle" =>
19 | "Too many login attempts. Please try again in :seconds seconds.",
20 |
21 | "name" => "Name",
22 | "email" => "Email",
23 | "company" => "Company",
24 | "password" => "Password",
25 | "confirm_password" => "Confirm Password",
26 | "already_registered" => "Already registered?",
27 | "register" => "Register",
28 |
29 | "remember_me" => "Remember me",
30 | "forgot_password" => "Forgot password?",
31 | "forgot_password_intro" => "Forgot your password? No problem. Just let us know your email address and we will email you a password reset link that will allow you to choose a new one.",
32 | "login" => "Login",
33 | "logout" => "Logout",
34 | "profile" => "Profile",
35 | "profile_information" => "Your account settings",
36 |
37 | "reset_password" => "Reset Password",
38 | "send_reset_password" => "Send Password Reset Email",
39 |
40 | "update_password" => "Update password",
41 | "password_info" => "Use a strong password with special characters to protect your account.",
42 | "current_password" => "Current password",
43 | "new_password" => "New password",
44 |
45 | "email_unverified" => "Your email address is not verified.",
46 | "resend_verification" => "Click here to receive a new verification email.",
47 | "verification_sent" => "A new verification link has been sent to your email.",
48 |
49 | "delete_account" => "Delete account",
50 | "delete_account_info" => "When your account is deleted, we will also delete all associated data. This cannot be recovered!",
51 | "delete_confirm" => "Are you sure you want to delete your account?",
52 | "cancel" => "Cancel",
53 |
54 | "admin" => "Admin",
55 | ];
56 |
--------------------------------------------------------------------------------
/lang/en/items.php:
--------------------------------------------------------------------------------
1 | 'Create Story',
5 | 'edit' => 'Edit Story',
6 |
7 | 'in_progress' => 'In Progress',
8 | 'in_progress_abbr' => 'P',
9 | 'done' => 'Done',
10 | 'done_abbr' => 'D',
11 | 'open' => 'Open',
12 | 'open_abbr' => 'O',
13 |
14 | 'markdown' => 'Markdown',
15 | 'comment' => 'Comment',
16 | 'send_comment' => 'Send comment',
17 |
18 | 'low' => 'Low',
19 | 'low_description' => 'Cosmetic/styling issues that do not affect the functionality of the system.',
20 | 'medium' => 'Medium',
21 | 'medium_description' => 'Features that may become important for future customers or project users.',
22 | 'high' => 'High',
23 | 'high_description' => 'Should be prioritized over regular tasks as the functionality is needed as soon as possible.',
24 | 'critical' => 'Critical',
25 | 'critical_description' => 'The system is not functioning or behaves completely incorrectly in critical situations.',
26 |
27 | 'bug' => 'Bug',
28 | 'bug_description' => 'A malfunctioning feature in the project.',
29 | 'feature' => 'Feature',
30 | 'feature_description' => 'A new functionality for the project.',
31 | 'task' => 'Task',
32 | 'task_description' => 'A general task for the system that neither fixes a bug nor adds a new feature.',
33 |
34 | ];
35 |
--------------------------------------------------------------------------------
/lang/en/pagination.php:
--------------------------------------------------------------------------------
1 | '« Previous',
17 | 'next' => 'Next »',
18 |
19 | ];
20 |
--------------------------------------------------------------------------------
/lang/en/passwords.php:
--------------------------------------------------------------------------------
1 | 'Your password has been reset.',
17 | 'sent' => 'We have emailed your password reset link.',
18 | 'throttled' => 'Please wait before retrying.',
19 | 'token' => 'This password reset token is invalid.',
20 | 'user' => "We can't find a user with that email address.",
21 |
22 | ];
23 |
--------------------------------------------------------------------------------
/lang/en/profile.php:
--------------------------------------------------------------------------------
1 | 'Profile',
5 | ];
6 |
--------------------------------------------------------------------------------
/lang/en/projects.php:
--------------------------------------------------------------------------------
1 | 'Project Overview',
5 | 'show' => 'View Project',
6 | 'create' => 'Create Project',
7 |
8 | 'logo' => 'Logo',
9 | 'description' => 'Description',
10 | 'story_format' => 'Story Format',
11 | 'save' => 'Save',
12 |
13 | 'back_to_overview' => 'Back to Overview',
14 | 'create_story' => '✨ Create User Story ✨',
15 |
16 | 'back_to_project' => 'Back to :project',
17 | ];
18 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "type": "module",
4 | "scripts": {
5 | "build": "vite build",
6 | "dev": "vite"
7 | },
8 | "devDependencies": {
9 | "@tailwindcss/forms": "^0.5.2",
10 | "alpinejs": "^3.4.2",
11 | "autoprefixer": "^10.4.2",
12 | "axios": "^1.7.4",
13 | "concurrently": "^9.0.1",
14 | "laravel-vite-plugin": "^1.0",
15 | "postcss": "^8.4.31",
16 | "tailwindcss": "^3.1.0",
17 | "vite": "^6.0"
18 | },
19 | "dependencies": {
20 | "tinymce": "^7.6.1"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 | tests/Unit
10 |
11 |
12 | tests/Feature
13 |
14 |
15 |
16 |
17 | app
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/public/.htaccess:
--------------------------------------------------------------------------------
1 |
2 |
3 | Options -MultiViews -Indexes
4 |
5 |
6 | RewriteEngine On
7 |
8 | # Handle Authorization Header
9 | RewriteCond %{HTTP:Authorization} .
10 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
11 |
12 | # Handle X-XSRF-Token Header
13 | RewriteCond %{HTTP:x-xsrf-token} .
14 | RewriteRule .* - [E=HTTP_X_XSRF_TOKEN:%{HTTP:X-XSRF-Token}]
15 |
16 | # Redirect Trailing Slashes If Not A Folder...
17 | RewriteCond %{REQUEST_FILENAME} !-d
18 | RewriteCond %{REQUEST_URI} (.+)/$
19 | RewriteRule ^ %1 [L,R=301]
20 |
21 | # Send Requests To Front Controller...
22 | RewriteCond %{REQUEST_FILENAME} !-d
23 | RewriteCond %{REQUEST_FILENAME} !-f
24 | RewriteRule ^ index.php [L]
25 |
26 |
--------------------------------------------------------------------------------
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1aWebmarketing/infiniteloop/a9e316b86b51fbe111f53a84f783792614918fe9/public/favicon.png
--------------------------------------------------------------------------------
/public/images/project-default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1aWebmarketing/infiniteloop/a9e316b86b51fbe111f53a84f783792614918fe9/public/images/project-default.png
--------------------------------------------------------------------------------
/public/index.php:
--------------------------------------------------------------------------------
1 | handleRequest(Request::capture());
18 |
--------------------------------------------------------------------------------
/public/js/tinymce/.npmignore:
--------------------------------------------------------------------------------
1 | composer.json
2 | bower.json
3 |
--------------------------------------------------------------------------------
/public/js/tinymce/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tinymce",
3 | "description": "Web based JavaScript HTML WYSIWYG editor control.",
4 | "license": "GPL-2.0-or-later",
5 | "keywords": [
6 | "wysiwyg",
7 | "tinymce",
8 | "richtext",
9 | "javascript",
10 | "html",
11 | "text",
12 | "rich editor",
13 | "rich text editor",
14 | "rte",
15 | "rich text",
16 | "contenteditable",
17 | "editing"
18 | ],
19 | "homepage": "https://www.tiny.cloud/",
20 | "ignore": [
21 | "README.md",
22 | "composer.json",
23 | "package.json",
24 | ".npmignore",
25 | "CHANGELOG.md"
26 | ]
27 | }
--------------------------------------------------------------------------------
/public/js/tinymce/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tinymce/tinymce",
3 | "version": "7.5.0",
4 | "description": "Web based JavaScript HTML WYSIWYG editor control.",
5 | "license": [
6 | "GPL-2.0-or-later"
7 | ],
8 | "keywords": [
9 | "wysiwyg",
10 | "tinymce",
11 | "richtext",
12 | "javascript",
13 | "html",
14 | "text",
15 | "rich editor",
16 | "rich text editor",
17 | "rte",
18 | "rich text",
19 | "contenteditable",
20 | "editing"
21 | ],
22 | "homepage": "https://www.tiny.cloud/",
23 | "type": "component",
24 | "extra": {
25 | "component": {
26 | "scripts": [
27 | "tinymce.js",
28 | "plugins/*/plugin.js",
29 | "themes/*/theme.js",
30 | "models/*/model.js",
31 | "icons/*/icons.js"
32 | ],
33 | "files": [
34 | "tinymce.min.js",
35 | "plugins/*/plugin.min.js",
36 | "themes/*/theme.min.js",
37 | "models/*/model.min.js",
38 | "skins/**",
39 | "icons/*/icons.min.js"
40 | ]
41 | }
42 | },
43 | "archive": {
44 | "exclude": [
45 | "README.md",
46 | "bower.js",
47 | "package.json",
48 | ".npmignore",
49 | "CHANGELOG.md"
50 | ]
51 | }
52 | }
--------------------------------------------------------------------------------
/public/js/tinymce/icons/default/index.js:
--------------------------------------------------------------------------------
1 | // Exports the "default" icons for usage with module loaders
2 | // Usage:
3 | // CommonJS:
4 | // require('tinymce/icons/default')
5 | // ES2015:
6 | // import 'tinymce/icons/default'
7 | require('./icons.js');
--------------------------------------------------------------------------------
/public/js/tinymce/license.md:
--------------------------------------------------------------------------------
1 | # Software License Agreement
2 |
3 | **TinyMCE** – [](https://github.com/tinymce/tinymce)
4 | Copyright (c) 2024, Ephox Corporation DBA Tiny Technologies, Inc.
5 |
6 | Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html).
7 |
--------------------------------------------------------------------------------
/public/js/tinymce/models/dom/index.js:
--------------------------------------------------------------------------------
1 | // Exports the "dom" model for usage with module loaders
2 | // Usage:
3 | // CommonJS:
4 | // require('tinymce/models/dom')
5 | // ES2015:
6 | // import 'tinymce/models/dom'
7 | require('./model.js');
--------------------------------------------------------------------------------
/public/js/tinymce/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tinymce",
3 | "version": "7.5.0",
4 | "repository": {
5 | "type": "git",
6 | "url": "https://github.com/tinymce/tinymce.git",
7 | "directory": "modules/tinymce"
8 | },
9 | "description": "Web based JavaScript HTML WYSIWYG editor control.",
10 | "author": "Ephox Corporation DBA Tiny Technologies, Inc",
11 | "main": "tinymce.js",
12 | "types": "tinymce.d.ts",
13 | "license": "GPL-2.0-or-later",
14 | "keywords": [
15 | "wysiwyg",
16 | "tinymce",
17 | "richtext",
18 | "javascript",
19 | "html",
20 | "text",
21 | "rich editor",
22 | "rich text editor",
23 | "rte",
24 | "rich text",
25 | "contenteditable",
26 | "editing"
27 | ],
28 | "homepage": "https://www.tiny.cloud/",
29 | "bugs": {
30 | "url": "https://github.com/tinymce/tinymce/issues"
31 | }
32 | }
--------------------------------------------------------------------------------
/public/js/tinymce/plugins/accordion/index.js:
--------------------------------------------------------------------------------
1 | // Exports the "accordion" plugin for usage with module loaders
2 | // Usage:
3 | // CommonJS:
4 | // require('tinymce/plugins/accordion')
5 | // ES2015:
6 | // import 'tinymce/plugins/accordion'
7 | require('./plugin.js');
--------------------------------------------------------------------------------
/public/js/tinymce/plugins/advlist/index.js:
--------------------------------------------------------------------------------
1 | // Exports the "advlist" plugin for usage with module loaders
2 | // Usage:
3 | // CommonJS:
4 | // require('tinymce/plugins/advlist')
5 | // ES2015:
6 | // import 'tinymce/plugins/advlist'
7 | require('./plugin.js');
--------------------------------------------------------------------------------
/public/js/tinymce/plugins/anchor/index.js:
--------------------------------------------------------------------------------
1 | // Exports the "anchor" plugin for usage with module loaders
2 | // Usage:
3 | // CommonJS:
4 | // require('tinymce/plugins/anchor')
5 | // ES2015:
6 | // import 'tinymce/plugins/anchor'
7 | require('./plugin.js');
--------------------------------------------------------------------------------
/public/js/tinymce/plugins/autolink/index.js:
--------------------------------------------------------------------------------
1 | // Exports the "autolink" plugin for usage with module loaders
2 | // Usage:
3 | // CommonJS:
4 | // require('tinymce/plugins/autolink')
5 | // ES2015:
6 | // import 'tinymce/plugins/autolink'
7 | require('./plugin.js');
--------------------------------------------------------------------------------
/public/js/tinymce/plugins/autoresize/index.js:
--------------------------------------------------------------------------------
1 | // Exports the "autoresize" plugin for usage with module loaders
2 | // Usage:
3 | // CommonJS:
4 | // require('tinymce/plugins/autoresize')
5 | // ES2015:
6 | // import 'tinymce/plugins/autoresize'
7 | require('./plugin.js');
--------------------------------------------------------------------------------
/public/js/tinymce/plugins/autosave/index.js:
--------------------------------------------------------------------------------
1 | // Exports the "autosave" plugin for usage with module loaders
2 | // Usage:
3 | // CommonJS:
4 | // require('tinymce/plugins/autosave')
5 | // ES2015:
6 | // import 'tinymce/plugins/autosave'
7 | require('./plugin.js');
--------------------------------------------------------------------------------
/public/js/tinymce/plugins/charmap/index.js:
--------------------------------------------------------------------------------
1 | // Exports the "charmap" plugin for usage with module loaders
2 | // Usage:
3 | // CommonJS:
4 | // require('tinymce/plugins/charmap')
5 | // ES2015:
6 | // import 'tinymce/plugins/charmap'
7 | require('./plugin.js');
--------------------------------------------------------------------------------
/public/js/tinymce/plugins/code/index.js:
--------------------------------------------------------------------------------
1 | // Exports the "code" plugin for usage with module loaders
2 | // Usage:
3 | // CommonJS:
4 | // require('tinymce/plugins/code')
5 | // ES2015:
6 | // import 'tinymce/plugins/code'
7 | require('./plugin.js');
--------------------------------------------------------------------------------
/public/js/tinymce/plugins/code/plugin.js:
--------------------------------------------------------------------------------
1 | /**
2 | * TinyMCE version 7.5.0 (2024-11-06)
3 | */
4 |
5 | (function () {
6 | 'use strict';
7 |
8 | var global = tinymce.util.Tools.resolve('tinymce.PluginManager');
9 |
10 | const setContent = (editor, html) => {
11 | editor.focus();
12 | editor.undoManager.transact(() => {
13 | editor.setContent(html);
14 | });
15 | editor.selection.setCursorLocation();
16 | editor.nodeChanged();
17 | };
18 | const getContent = editor => {
19 | return editor.getContent({ source_view: true });
20 | };
21 |
22 | const open = editor => {
23 | const editorContent = getContent(editor);
24 | editor.windowManager.open({
25 | title: 'Source Code',
26 | size: 'large',
27 | body: {
28 | type: 'panel',
29 | items: [{
30 | type: 'textarea',
31 | name: 'code'
32 | }]
33 | },
34 | buttons: [
35 | {
36 | type: 'cancel',
37 | name: 'cancel',
38 | text: 'Cancel'
39 | },
40 | {
41 | type: 'submit',
42 | name: 'save',
43 | text: 'Save',
44 | primary: true
45 | }
46 | ],
47 | initialData: { code: editorContent },
48 | onSubmit: api => {
49 | setContent(editor, api.getData().code);
50 | api.close();
51 | }
52 | });
53 | };
54 |
55 | const register$1 = editor => {
56 | editor.addCommand('mceCodeEditor', () => {
57 | open(editor);
58 | });
59 | };
60 |
61 | const register = editor => {
62 | const onAction = () => editor.execCommand('mceCodeEditor');
63 | editor.ui.registry.addButton('code', {
64 | icon: 'sourcecode',
65 | tooltip: 'Source code',
66 | onAction
67 | });
68 | editor.ui.registry.addMenuItem('code', {
69 | icon: 'sourcecode',
70 | text: 'Source code',
71 | onAction
72 | });
73 | };
74 |
75 | var Plugin = () => {
76 | global.add('code', editor => {
77 | register$1(editor);
78 | register(editor);
79 | return {};
80 | });
81 | };
82 |
83 | Plugin();
84 |
85 | })();
86 |
--------------------------------------------------------------------------------
/public/js/tinymce/plugins/code/plugin.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * TinyMCE version 7.5.0 (2024-11-06)
3 | */
4 | !function(){"use strict";tinymce.util.Tools.resolve("tinymce.PluginManager").add("code",(e=>((e=>{e.addCommand("mceCodeEditor",(()=>{(e=>{const o=(e=>e.getContent({source_view:!0}))(e);e.windowManager.open({title:"Source Code",size:"large",body:{type:"panel",items:[{type:"textarea",name:"code"}]},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:{code:o},onSubmit:o=>{((e,o)=>{e.focus(),e.undoManager.transact((()=>{e.setContent(o)})),e.selection.setCursorLocation(),e.nodeChanged()})(e,o.getData().code),o.close()}})})(e)}))})(e),(e=>{const o=()=>e.execCommand("mceCodeEditor");e.ui.registry.addButton("code",{icon:"sourcecode",tooltip:"Source code",onAction:o}),e.ui.registry.addMenuItem("code",{icon:"sourcecode",text:"Source code",onAction:o})})(e),{})))}();
--------------------------------------------------------------------------------
/public/js/tinymce/plugins/codesample/index.js:
--------------------------------------------------------------------------------
1 | // Exports the "codesample" plugin for usage with module loaders
2 | // Usage:
3 | // CommonJS:
4 | // require('tinymce/plugins/codesample')
5 | // ES2015:
6 | // import 'tinymce/plugins/codesample'
7 | require('./plugin.js');
--------------------------------------------------------------------------------
/public/js/tinymce/plugins/directionality/index.js:
--------------------------------------------------------------------------------
1 | // Exports the "directionality" plugin for usage with module loaders
2 | // Usage:
3 | // CommonJS:
4 | // require('tinymce/plugins/directionality')
5 | // ES2015:
6 | // import 'tinymce/plugins/directionality'
7 | require('./plugin.js');
--------------------------------------------------------------------------------
/public/js/tinymce/plugins/emoticons/index.js:
--------------------------------------------------------------------------------
1 | // Exports the "emoticons" plugin for usage with module loaders
2 | // Usage:
3 | // CommonJS:
4 | // require('tinymce/plugins/emoticons')
5 | // ES2015:
6 | // import 'tinymce/plugins/emoticons'
7 | require('./plugin.js');
--------------------------------------------------------------------------------
/public/js/tinymce/plugins/fullscreen/index.js:
--------------------------------------------------------------------------------
1 | // Exports the "fullscreen" plugin for usage with module loaders
2 | // Usage:
3 | // CommonJS:
4 | // require('tinymce/plugins/fullscreen')
5 | // ES2015:
6 | // import 'tinymce/plugins/fullscreen'
7 | require('./plugin.js');
--------------------------------------------------------------------------------
/public/js/tinymce/plugins/help/index.js:
--------------------------------------------------------------------------------
1 | // Exports the "help" plugin for usage with module loaders
2 | // Usage:
3 | // CommonJS:
4 | // require('tinymce/plugins/help')
5 | // ES2015:
6 | // import 'tinymce/plugins/help'
7 | require('./plugin.js');
--------------------------------------------------------------------------------
/public/js/tinymce/plugins/image/index.js:
--------------------------------------------------------------------------------
1 | // Exports the "image" plugin for usage with module loaders
2 | // Usage:
3 | // CommonJS:
4 | // require('tinymce/plugins/image')
5 | // ES2015:
6 | // import 'tinymce/plugins/image'
7 | require('./plugin.js');
--------------------------------------------------------------------------------
/public/js/tinymce/plugins/importcss/index.js:
--------------------------------------------------------------------------------
1 | // Exports the "importcss" plugin for usage with module loaders
2 | // Usage:
3 | // CommonJS:
4 | // require('tinymce/plugins/importcss')
5 | // ES2015:
6 | // import 'tinymce/plugins/importcss'
7 | require('./plugin.js');
--------------------------------------------------------------------------------
/public/js/tinymce/plugins/insertdatetime/index.js:
--------------------------------------------------------------------------------
1 | // Exports the "insertdatetime" plugin for usage with module loaders
2 | // Usage:
3 | // CommonJS:
4 | // require('tinymce/plugins/insertdatetime')
5 | // ES2015:
6 | // import 'tinymce/plugins/insertdatetime'
7 | require('./plugin.js');
--------------------------------------------------------------------------------
/public/js/tinymce/plugins/link/index.js:
--------------------------------------------------------------------------------
1 | // Exports the "link" plugin for usage with module loaders
2 | // Usage:
3 | // CommonJS:
4 | // require('tinymce/plugins/link')
5 | // ES2015:
6 | // import 'tinymce/plugins/link'
7 | require('./plugin.js');
--------------------------------------------------------------------------------
/public/js/tinymce/plugins/lists/index.js:
--------------------------------------------------------------------------------
1 | // Exports the "lists" plugin for usage with module loaders
2 | // Usage:
3 | // CommonJS:
4 | // require('tinymce/plugins/lists')
5 | // ES2015:
6 | // import 'tinymce/plugins/lists'
7 | require('./plugin.js');
--------------------------------------------------------------------------------
/public/js/tinymce/plugins/media/index.js:
--------------------------------------------------------------------------------
1 | // Exports the "media" plugin for usage with module loaders
2 | // Usage:
3 | // CommonJS:
4 | // require('tinymce/plugins/media')
5 | // ES2015:
6 | // import 'tinymce/plugins/media'
7 | require('./plugin.js');
--------------------------------------------------------------------------------
/public/js/tinymce/plugins/nonbreaking/index.js:
--------------------------------------------------------------------------------
1 | // Exports the "nonbreaking" plugin for usage with module loaders
2 | // Usage:
3 | // CommonJS:
4 | // require('tinymce/plugins/nonbreaking')
5 | // ES2015:
6 | // import 'tinymce/plugins/nonbreaking'
7 | require('./plugin.js');
--------------------------------------------------------------------------------
/public/js/tinymce/plugins/nonbreaking/plugin.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * TinyMCE version 7.5.0 (2024-11-06)
3 | */
4 | !function(){"use strict";var n=tinymce.util.Tools.resolve("tinymce.PluginManager");const e=n=>e=>typeof e===n,o=e("boolean"),a=e("number"),t=n=>e=>e.options.get(n),i=t("nonbreaking_force_tab"),s=t("nonbreaking_wrap"),r=(n,e)=>{let o="";for(let a=0;a{const o=s(n)||n.plugins.visualchars?`${r(" ",e)} `:r(" ",e);n.undoManager.transact((()=>n.insertContent(o)))};var l=tinymce.util.Tools.resolve("tinymce.util.VK");const u=n=>e=>{const o=()=>{e.setEnabled(n.selection.isEditable())};return n.on("NodeChange",o),o(),()=>{n.off("NodeChange",o)}};n.add("nonbreaking",(n=>{(n=>{const e=n.options.register;e("nonbreaking_force_tab",{processor:n=>o(n)?{value:n?3:0,valid:!0}:a(n)?{value:n,valid:!0}:{valid:!1,message:"Must be a boolean or number."},default:!1}),e("nonbreaking_wrap",{processor:"boolean",default:!0})})(n),(n=>{n.addCommand("mceNonBreaking",(()=>{c(n,1)}))})(n),(n=>{const e=()=>n.execCommand("mceNonBreaking");n.ui.registry.addButton("nonbreaking",{icon:"non-breaking",tooltip:"Nonbreaking space",onAction:e,onSetup:u(n)}),n.ui.registry.addMenuItem("nonbreaking",{icon:"non-breaking",text:"Nonbreaking space",onAction:e,onSetup:u(n)})})(n),(n=>{const e=i(n);e>0&&n.on("keydown",(o=>{if(o.keyCode===l.TAB&&!o.isDefaultPrevented()){if(o.shiftKey)return;o.preventDefault(),o.stopImmediatePropagation(),c(n,e)}}))})(n)}))}();
--------------------------------------------------------------------------------
/public/js/tinymce/plugins/pagebreak/index.js:
--------------------------------------------------------------------------------
1 | // Exports the "pagebreak" plugin for usage with module loaders
2 | // Usage:
3 | // CommonJS:
4 | // require('tinymce/plugins/pagebreak')
5 | // ES2015:
6 | // import 'tinymce/plugins/pagebreak'
7 | require('./plugin.js');
--------------------------------------------------------------------------------
/public/js/tinymce/plugins/pagebreak/plugin.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * TinyMCE version 7.5.0 (2024-11-06)
3 | */
4 | !function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),a=tinymce.util.Tools.resolve("tinymce.Env");const t=e=>a=>a.options.get(e),n=t("pagebreak_separator"),o=t("pagebreak_split_block"),r="mce-pagebreak",s=e=>{const t=` `;return e?`${t}
`:t},c=e=>a=>{const t=()=>{a.setEnabled(e.selection.isEditable())};return e.on("NodeChange",t),t(),()=>{e.off("NodeChange",t)}};e.add("pagebreak",(e=>{(e=>{const a=e.options.register;a("pagebreak_separator",{processor:"string",default:"\x3c!-- pagebreak --\x3e"}),a("pagebreak_split_block",{processor:"boolean",default:!1})})(e),(e=>{e.addCommand("mcePageBreak",(()=>{e.insertContent(s(o(e)))}))})(e),(e=>{const a=()=>e.execCommand("mcePageBreak");e.ui.registry.addButton("pagebreak",{icon:"page-break",tooltip:"Page break",onAction:a,onSetup:c(e)}),e.ui.registry.addMenuItem("pagebreak",{text:"Page break",icon:"page-break",onAction:a,onSetup:c(e)})})(e),(e=>{const a=n(e),t=()=>o(e),c=new RegExp(a.replace(/[\?\.\*\[\]\(\)\{\}\+\^\$\:]/g,(e=>"\\"+e)),"gi");e.on("BeforeSetContent",(e=>{e.content=e.content.replace(c,s(t()))})),e.on("PreInit",(()=>{e.serializer.addNodeFilter("img",(n=>{let o,s,c=n.length;for(;c--;)if(o=n[c],s=o.attr("class"),s&&-1!==s.indexOf(r)){const n=o.parent;if(n&&e.schema.getBlockElements()[n.name]&&t()){n.type=3,n.value=a,n.raw=!0,o.remove();continue}o.type=3,o.value=a,o.raw=!0}}))}))})(e),(e=>{e.on("ResolveName",(a=>{"IMG"===a.target.nodeName&&e.dom.hasClass(a.target,r)&&(a.name="pagebreak")}))})(e)}))}();
--------------------------------------------------------------------------------
/public/js/tinymce/plugins/preview/index.js:
--------------------------------------------------------------------------------
1 | // Exports the "preview" plugin for usage with module loaders
2 | // Usage:
3 | // CommonJS:
4 | // require('tinymce/plugins/preview')
5 | // ES2015:
6 | // import 'tinymce/plugins/preview'
7 | require('./plugin.js');
--------------------------------------------------------------------------------
/public/js/tinymce/plugins/preview/plugin.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * TinyMCE version 7.5.0 (2024-11-06)
3 | */
4 | !function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),t=tinymce.util.Tools.resolve("tinymce.Env"),n=tinymce.util.Tools.resolve("tinymce.util.Tools");const o=e=>t=>t.options.get(e),i=o("content_style"),s=o("content_css_cors"),c=o("body_class"),r=o("body_id");e.add("preview",(e=>{(e=>{e.addCommand("mcePreview",(()=>{(e=>{const o=(e=>{var o;let a="";const l=e.dom.encode,d=null!==(o=i(e))&&void 0!==o?o:"";a+=' ';const m=s(e)?' crossorigin="anonymous"':"";n.each(e.contentCSS,(t=>{a+=' "})),d&&(a+='");const y=r(e),u=c(e),v='
2 |
11 |
--------------------------------------------------------------------------------
/resources/views/components/icons/comments.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/components/icons/delete.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/resources/views/components/icons/edit.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/resources/views/components/icons/items.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/resources/views/components/icons/markdown.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/resources/views/components/icons/right-arrow.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/resources/views/components/icons/up-arrow-green.blade.php:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/resources/views/components/icons/up-arrow.blade.php:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/resources/views/components/input-error.blade.php:
--------------------------------------------------------------------------------
1 | @props(['messages'])
2 |
3 | @if ($messages)
4 | merge(['class' => 'text-sm text-red-600 space-y-1']) }}>
5 | @foreach ((array) $messages as $message)
6 | {{ $message }}
7 | @endforeach
8 |
9 | @endif
10 |
--------------------------------------------------------------------------------
/resources/views/components/input-group.blade.php:
--------------------------------------------------------------------------------
1 | @props([
2 | 'id' => '',
3 | 'label' => false,
4 | 'disabled' => false,
5 | 'messages' => NULL,
6 | 'required' => NULL,
7 | 'readonly' => NULL,
8 | 'type' => 'text',
9 | 'name' => '',
10 | 'description' => '',
11 | 'wrapperClass' => '',
12 | ])
13 | @php
14 | $baseClasses = 'border-gray-300 w-full pb-2 pt-3 focus:border-main focus:ring-main-500 rounded-sm shadow-sm';
15 | if( $readonly || $disabled )
16 | {
17 | $baseClasses = 'border-gray-300 bg-gray-200 w-full pb-2 pt-3 focus:border-transparent focus:ring-0 rounded-sm shadow-sm';
18 | }
19 | @endphp
20 |
21 |
22 | @if($label)
23 |
24 | {{$label}}
25 | @if($required)
26 | *
27 | @endif
28 |
29 | @endif
30 |
merge(['class' => $baseClasses]) !!}>
32 |
33 | @if($description !== '')
34 |
{{ $description }}
35 | @endif
36 |
37 | @if($errors->has($name))
38 |
39 | @foreach ($errors->get($name) as $error)
40 | {{ $error }}
41 | @endforeach
42 |
43 | @endif
44 |
45 |
46 |
--------------------------------------------------------------------------------
/resources/views/components/input-label.blade.php:
--------------------------------------------------------------------------------
1 | @props(['value'])
2 |
3 | merge(['class' => 'block font-medium text-sm text-gray-700']) }}>
4 | {{ $value ?? $slot }}
5 |
6 |
--------------------------------------------------------------------------------
/resources/views/components/item-priority.blade.php:
--------------------------------------------------------------------------------
1 | @props([
2 | 'name' => '',
3 | 'value' => '',
4 | ])
5 |
12 |
13 |
18 | {{ __('items.low') }}
19 | {{ __('items.low_description') }}
20 |
21 |
22 |
27 | {{ __('items.medium') }}
28 | {{ __('items.medium_description') }}
29 |
30 |
31 |
36 | {{ __('items.high') }}
37 | {{ __('items.high_description') }}
38 |
39 |
40 |
45 | {{ __('items.critical') }}
46 | {{ __('items.critical_description') }}
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/resources/views/components/item-table.blade.php:
--------------------------------------------------------------------------------
1 | merge(['class' => 'flex gap-4 mb-2 border-b pb-2 px-2 pt-2']) }}>
2 |
3 |
13 |
{{ $item->voting }}
14 |
15 |
16 |
23 |
24 |
25 |
{{ $item->comments()->count() }}
26 |
27 |
28 |
--------------------------------------------------------------------------------
/resources/views/components/item-type.blade.php:
--------------------------------------------------------------------------------
1 | @props([
2 | 'name' => '',
3 | 'value' => '',
4 | ])
5 |
12 |
13 |
18 | {{ __('items.bug') }}
19 | {{ __('items.bug_description') }}
20 |
21 |
22 |
27 | {{ __('items.feature') }}
28 | {{ __('items.feature_description') }}
29 |
30 |
31 |
36 | {{ __('items.task') }}
37 | {{ __('items.task_description') }}
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/resources/views/components/nav-link.blade.php:
--------------------------------------------------------------------------------
1 | @props(['active'])
2 |
3 | @php
4 | $classes = ($active ?? false)
5 | ? 'inline-flex items-center px-1 pt-1 border-b-2 border-indigo-400 dark:border-indigo-600 text-sm font-medium leading-5 text-gray-900 dark:text-gray-100 focus:outline-none focus:border-indigo-700 transition duration-150 ease-in-out'
6 | : 'inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hover:border-gray-300 dark:hover:border-gray-700 focus:outline-none focus:text-gray-700 dark:focus:text-gray-300 focus:border-gray-300 dark:focus:border-gray-700 transition duration-150 ease-in-out';
7 | @endphp
8 |
9 | merge(['class' => $classes]) }}>
10 | {{ $slot }}
11 |
12 |
--------------------------------------------------------------------------------
/resources/views/components/primary-button.blade.php:
--------------------------------------------------------------------------------
1 | merge(['type' => 'submit', 'class' => 'inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 focus:bg-gray-700 active:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition ease-in-out duration-150']) }}>
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/components/primary-link-button.blade.php:
--------------------------------------------------------------------------------
1 | @props([
2 | 'href' => '',
3 | ])
4 | merge(['type' => 'submit', 'type' => 'submit', 'class' => 'inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 focus:bg-gray-700 active:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition ease-in-out duration-150']) }}>
5 | {{ $slot }}
6 |
7 |
--------------------------------------------------------------------------------
/resources/views/components/responsive-nav-link.blade.php:
--------------------------------------------------------------------------------
1 | @props(['active'])
2 |
3 | @php
4 | $classes = ($active ?? false)
5 | ? 'block w-full ps-3 pe-4 py-2 border-l-4 border-indigo-400 dark:border-indigo-600 text-start text-base font-medium text-indigo-700 dark:text-indigo-300 bg-indigo-50 dark:bg-indigo-900/50 focus:outline-none focus:text-indigo-800 dark:focus:text-indigo-200 focus:bg-indigo-100 dark:focus:bg-indigo-900 focus:border-indigo-700 dark:focus:border-indigo-300 transition duration-150 ease-in-out'
6 | : 'block w-full ps-3 pe-4 py-2 border-l-4 border-transparent text-start text-base font-medium text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700 hover:border-gray-300 dark:hover:border-gray-600 focus:outline-none focus:text-gray-800 dark:focus:text-gray-200 focus:bg-gray-50 dark:focus:bg-gray-700 focus:border-gray-300 dark:focus:border-gray-600 transition duration-150 ease-in-out';
7 | @endphp
8 |
9 | merge(['class' => $classes]) }}>
10 | {{ $slot }}
11 |
12 |
--------------------------------------------------------------------------------
/resources/views/components/secondary-button.blade.php:
--------------------------------------------------------------------------------
1 | merge(['type' => 'button', 'class' => 'inline-flex items-center px-4 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-500 rounded-md font-semibold text-xs text-gray-700 dark:text-gray-300 uppercase tracking-widest shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 disabled:opacity-25 transition ease-in-out duration-150']) }}>
2 | {{ $slot }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/components/select-group.blade.php:
--------------------------------------------------------------------------------
1 | @props(['id' => '' , 'label' => false , 'disabled' => false, 'messages' => NULL, 'required' => NULL , 'name' => '', 'options' => '', 'value' => '' ])
2 |
3 | @php
4 | $renderOptions = array();
5 | if(!empty($options))
6 | {
7 | $tmpOptions = explode(";", $options);
8 |
9 | foreach($tmpOptions as $option)
10 | {
11 | list($tmpVal, $tmpLabel) = explode(":", $option);
12 | $renderOptions[$tmpVal] = $tmpLabel;
13 | }
14 | }
15 | @endphp
16 |
17 |
18 |
19 | @if($label)
20 |
21 | {{ $label }}
22 | @if($required)
23 | *
24 | @endif
25 |
26 | @endif
27 |
merge(['class' => 'border-gray-300 w-full pb-2 pt-3 focus:border-main focus:ring-main-500 rounded-sm shadow-sm']) !!}
32 | >
33 | @foreach($renderOptions as $val => $label)
34 | {{ __($label) }}
35 | @endforeach
36 |
37 |
38 | @if($errors->has($name))
39 |
40 | @foreach ($errors->get($name) as $error)
41 | {{ $error }}
42 | @endforeach
43 |
44 | @endif
45 |
46 |
47 |
--------------------------------------------------------------------------------
/resources/views/components/text-input.blade.php:
--------------------------------------------------------------------------------
1 | @props(['disabled' => false])
2 |
3 | merge(['class' => 'border-gray-300 focus:border-primary focus:ring-primary rounded-md shadow-sm mb-2']) !!}>
4 |
--------------------------------------------------------------------------------
/resources/views/components/textarea-group.blade.php:
--------------------------------------------------------------------------------
1 | @props([
2 | 'id' => '' ,
3 | 'label' => false ,
4 | 'disabled' => false,
5 | 'messages' => NULL,
6 | 'required' => NULL ,
7 | 'name' => '',
8 | 'description' => ''
9 | ])
10 |
11 |
12 |
13 | @if($label)
14 |
15 | {{ $label }}
16 | @if($required)
17 | *
18 | @endif
19 |
20 | @endif
21 |
26 |
27 | @if($description !== '')
28 |
{{ $description }}
29 | @endif
30 |
31 | @if($errors->has($name))
32 |
33 | @foreach ($errors->get($name) as $error)
34 | {{ $error }}
35 | @endforeach
36 |
37 | @endif
38 |
39 |
40 |
--------------------------------------------------------------------------------
/resources/views/components/textarea.blade.php:
--------------------------------------------------------------------------------
1 | @props([
2 | 'name' => '',
3 | ])
4 |
5 |
--------------------------------------------------------------------------------
/resources/views/emails/item-new-comment.blade.php:
--------------------------------------------------------------------------------
1 | {{ $comment_text }}
2 | Von: {{ $author_name }}
3 | Link zur User Story
4 |
5 | infiniteloop
6 |
--------------------------------------------------------------------------------
/resources/views/emails/item-new-status.blade.php:
--------------------------------------------------------------------------------
1 | {{ $old_status }} > {{ $new_status }}
2 | Link zur User Story
3 |
4 | infiniteloop
5 |
--------------------------------------------------------------------------------
/resources/views/errors/401.blade.php:
--------------------------------------------------------------------------------
1 | @extends('errors::minimal')
2 |
3 | @section('title', __('Unauthorized'))
4 | @section('code', '401')
5 | @section('message', __('Unauthorized'))
6 |
--------------------------------------------------------------------------------
/resources/views/errors/402.blade.php:
--------------------------------------------------------------------------------
1 | @extends('errors::minimal')
2 |
3 | @section('title', __('Payment Required'))
4 | @section('code', '402')
5 | @section('message', __('Payment Required'))
6 |
--------------------------------------------------------------------------------
/resources/views/errors/403.blade.php:
--------------------------------------------------------------------------------
1 | @extends('errors::minimal')
2 |
3 | @section('title', __('Forbidden'))
4 | @section('code', '403')
5 | @section('message', __($exception->getMessage() ?: 'Forbidden'))
6 |
--------------------------------------------------------------------------------
/resources/views/errors/404.blade.php:
--------------------------------------------------------------------------------
1 | @extends('errors::minimal')
2 |
3 | @section('title', __('Not Found'))
4 | @section('code', '404')
5 | @section('message', __('Not Found'))
6 |
--------------------------------------------------------------------------------
/resources/views/errors/419.blade.php:
--------------------------------------------------------------------------------
1 | @extends('errors::minimal')
2 |
3 | @section('title', __('Page Expired'))
4 | @section('code', '419')
5 | @section('message', __('Page Expired'))
6 |
--------------------------------------------------------------------------------
/resources/views/errors/429.blade.php:
--------------------------------------------------------------------------------
1 | @extends('errors::minimal')
2 |
3 | @section('title', __('Too Many Requests'))
4 | @section('code', '429')
5 | @section('message', __('Too Many Requests'))
6 |
--------------------------------------------------------------------------------
/resources/views/errors/500.blade.php:
--------------------------------------------------------------------------------
1 | @extends('errors::minimal')
2 |
3 | @section('title', __('Server Error'))
4 | @section('code', '500')
5 | @section('message', __('Server Error'))
6 |
--------------------------------------------------------------------------------
/resources/views/errors/503.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | We'll Be Back Soon!
7 |
30 |
31 |
32 |
33 |
🚧 We'll Be Back Soon!
34 |
Our website is currently undergoing maintenance. We should be back shortly. Thank you for your patience!
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/resources/views/errors/layout.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | @yield('title')
8 |
9 |
10 |
43 |
44 |
45 |
46 |
47 |
48 | @yield('message')
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/resources/views/items/form.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
42 |
43 |
44 | Medien
45 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/resources/views/layouts/guest.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | {{ config('app.name', 'adfinity') }}
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | @vite(['resources/css/app.css', 'resources/js/app.js'])
18 | @livewireStyles
19 |
20 |
21 |
22 |
23 |
24 |
25 | {{ $slot }}
26 |
27 |
28 | @livewireScripts
29 |
30 |
31 |
--------------------------------------------------------------------------------
/resources/views/pages/changelog.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Changelog
5 |
6 |
7 |
8 |
9 |
10 | {!! $changelog !!}
11 |
12 |
13 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/resources/views/profile/edit.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 | @include('profile.partials.update-profile-information-form')
12 |
13 |
14 |
15 |
16 | @include('profile.partials.update-password-form')
17 |
18 |
19 |
20 | @include('profile.partials.delete-user-form')
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/resources/views/profile/partials/delete-user-form.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ __('Delete Account') }}
5 |
6 |
7 |
8 | {{ __('Once your account is deleted, all of its resources and data will be permanently deleted. Before deleting your account, please download any data or information that you wish to retain.') }}
9 |
10 |
11 |
12 | {{ __('Delete Account') }}
16 |
17 |
18 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/resources/views/profile/partials/update-password-form.blade.php:
--------------------------------------------------------------------------------
1 |
49 |
--------------------------------------------------------------------------------
/resources/views/projects/form.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ __('projects.create') }}
6 |
7 |
8 |
9 |
10 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/resources/views/projects/show.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
14 |
15 | {{--
Alle anzeigen
16 |
Abgeschlossene Items --}}
17 |
18 |
{{ __('projects.create_story') }}
19 |
20 |
21 |
22 | @if( $activeItems->count() )
23 |
{{ __('items.in_progress') }}
24 | @foreach($activeItems as $activeItem)
25 |
26 | @endforeach
27 | @endif
28 |
29 | @if( $createdItems->count() )
30 |
{{ __('items.open') }}
31 | @foreach($createdItems as $createdItem)
32 |
33 | @endforeach
34 | @endif
35 |
36 | @if( $doneItems->count() )
37 |
{{ __('items.done') }}
38 | @foreach($doneItems as $doneItem)
39 |
40 | @endforeach
41 | @endif
42 |
43 |
44 |
--------------------------------------------------------------------------------
/routes/console.php:
--------------------------------------------------------------------------------
1 | everyMinute()->name('QueueWorker::_invoke');
7 |
--------------------------------------------------------------------------------
/storage/app/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !private/
3 | !public/
4 | !.gitignore
5 |
--------------------------------------------------------------------------------
/storage/app/private/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/app/public/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/.gitignore:
--------------------------------------------------------------------------------
1 | compiled.php
2 | config.php
3 | down
4 | events.scanned.php
5 | maintenance.php
6 | routes.php
7 | routes.scanned.php
8 | schedule-*
9 | services.json
10 |
--------------------------------------------------------------------------------
/storage/framework/cache/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !data/
3 | !.gitignore
4 |
--------------------------------------------------------------------------------
/storage/framework/cache/data/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/sessions/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/testing/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/views/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/logs/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | import defaultTheme from 'tailwindcss/defaultTheme';
2 | import forms from '@tailwindcss/forms';
3 |
4 | /** @type {import('tailwindcss').Config} */
5 | export default {
6 | content: [
7 | './vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php',
8 | './storage/framework/views/*.php',
9 | './resources/views/**/*.blade.php',
10 | './app/Models/*.php',
11 | ],
12 |
13 | theme: {
14 | extend: {
15 | fontFamily: {
16 | sans: ['Inter', ...defaultTheme.fontFamily.sans],
17 | },
18 | },
19 | },
20 |
21 | plugins: [forms],
22 | };
23 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/AuthenticationTest.php:
--------------------------------------------------------------------------------
1 | get('/login');
7 |
8 | $response->assertStatus(200);
9 | });
10 |
11 | test('users can authenticate using the login screen', function () {
12 | $user = User::factory()->create();
13 |
14 | $response = $this->post('/login', [
15 | 'email' => $user->email,
16 | 'password' => 'password',
17 | ]);
18 |
19 | $this->assertAuthenticated();
20 | $response->assertRedirect(route('dashboard', absolute: false));
21 | });
22 |
23 | test('users can not authenticate with invalid password', function () {
24 | $user = User::factory()->create();
25 |
26 | $this->post('/login', [
27 | 'email' => $user->email,
28 | 'password' => 'wrong-password',
29 | ]);
30 |
31 | $this->assertGuest();
32 | });
33 |
34 | test('users can logout', function () {
35 | $user = User::factory()->create();
36 |
37 | $response = $this->actingAs($user)->post('/logout');
38 |
39 | $this->assertGuest();
40 | $response->assertRedirect('/');
41 | });
42 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/EmailVerificationTest.php:
--------------------------------------------------------------------------------
1 | unverified()->create();
10 |
11 | $response = $this->actingAs($user)->get('/verify-email');
12 |
13 | $response->assertStatus(200);
14 | });
15 |
16 | test('email can be verified', function () {
17 | $user = User::factory()->unverified()->create();
18 |
19 | Event::fake();
20 |
21 | $verificationUrl = URL::temporarySignedRoute(
22 | 'verification.verify',
23 | now()->addMinutes(60),
24 | ['id' => $user->id, 'hash' => sha1($user->email)]
25 | );
26 |
27 | $response = $this->actingAs($user)->get($verificationUrl);
28 |
29 | Event::assertDispatched(Verified::class);
30 | expect($user->fresh()->hasVerifiedEmail())->toBeTrue();
31 | $response->assertRedirect(route('dashboard', absolute: false).'?verified=1');
32 | });
33 |
34 | test('email is not verified with invalid hash', function () {
35 | $user = User::factory()->unverified()->create();
36 |
37 | $verificationUrl = URL::temporarySignedRoute(
38 | 'verification.verify',
39 | now()->addMinutes(60),
40 | ['id' => $user->id, 'hash' => sha1('wrong-email')]
41 | );
42 |
43 | $this->actingAs($user)->get($verificationUrl);
44 |
45 | expect($user->fresh()->hasVerifiedEmail())->toBeFalse();
46 | });
47 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/PasswordConfirmationTest.php:
--------------------------------------------------------------------------------
1 | create();
7 |
8 | $response = $this->actingAs($user)->get('/confirm-password');
9 |
10 | $response->assertStatus(200);
11 | });
12 |
13 | test('password can be confirmed', function () {
14 | $user = User::factory()->create();
15 |
16 | $response = $this->actingAs($user)->post('/confirm-password', [
17 | 'password' => 'password',
18 | ]);
19 |
20 | $response->assertRedirect();
21 | $response->assertSessionHasNoErrors();
22 | });
23 |
24 | test('password is not confirmed with invalid password', function () {
25 | $user = User::factory()->create();
26 |
27 | $response = $this->actingAs($user)->post('/confirm-password', [
28 | 'password' => 'wrong-password',
29 | ]);
30 |
31 | $response->assertSessionHasErrors();
32 | });
33 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/PasswordResetTest.php:
--------------------------------------------------------------------------------
1 | get('/forgot-password');
9 |
10 | $response->assertStatus(200);
11 | });
12 |
13 | test('reset password link can be requested', function () {
14 | Notification::fake();
15 |
16 | $user = User::factory()->create();
17 |
18 | $this->post('/forgot-password', ['email' => $user->email]);
19 |
20 | Notification::assertSentTo($user, ResetPassword::class);
21 | });
22 |
23 | test('reset password screen can be rendered', function () {
24 | Notification::fake();
25 |
26 | $user = User::factory()->create();
27 |
28 | $this->post('/forgot-password', ['email' => $user->email]);
29 |
30 | Notification::assertSentTo($user, ResetPassword::class, function ($notification) {
31 | $response = $this->get('/reset-password/'.$notification->token);
32 |
33 | $response->assertStatus(200);
34 |
35 | return true;
36 | });
37 | });
38 |
39 | test('password can be reset with valid token', function () {
40 | Notification::fake();
41 |
42 | $user = User::factory()->create();
43 |
44 | $this->post('/forgot-password', ['email' => $user->email]);
45 |
46 | Notification::assertSentTo($user, ResetPassword::class, function ($notification) use ($user) {
47 | $response = $this->post('/reset-password', [
48 | 'token' => $notification->token,
49 | 'email' => $user->email,
50 | 'password' => 'password',
51 | 'password_confirmation' => 'password',
52 | ]);
53 |
54 | $response
55 | ->assertSessionHasNoErrors()
56 | ->assertRedirect(route('login'));
57 |
58 | return true;
59 | });
60 | });
61 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/PasswordUpdateTest.php:
--------------------------------------------------------------------------------
1 | create();
8 |
9 | $response = $this
10 | ->actingAs($user)
11 | ->from('/profile')
12 | ->put('/password', [
13 | 'current_password' => 'password',
14 | 'password' => 'new-password',
15 | 'password_confirmation' => 'new-password',
16 | ]);
17 |
18 | $response
19 | ->assertSessionHasNoErrors()
20 | ->assertRedirect('/profile');
21 |
22 | $this->assertTrue(Hash::check('new-password', $user->refresh()->password));
23 | });
24 |
25 | test('correct password must be provided to update password', function () {
26 | $user = User::factory()->create();
27 |
28 | $response = $this
29 | ->actingAs($user)
30 | ->from('/profile')
31 | ->put('/password', [
32 | 'current_password' => 'wrong-password',
33 | 'password' => 'new-password',
34 | 'password_confirmation' => 'new-password',
35 | ]);
36 |
37 | $response
38 | ->assertSessionHasErrorsIn('updatePassword', 'current_password')
39 | ->assertRedirect('/profile');
40 | });
41 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/RegistrationTest.php:
--------------------------------------------------------------------------------
1 | get('/register');
5 |
6 | $response->assertStatus(200);
7 | });
8 |
9 | test('new users can register', function () {
10 | $response = $this->post('/register', [
11 | 'name' => 'Test User',
12 | 'email' => 'test@example.com',
13 | 'password' => 'password',
14 | 'password_confirmation' => 'password',
15 | ]);
16 |
17 | $this->assertAuthenticated();
18 | $response->assertRedirect(route('dashboard', absolute: false));
19 | });
20 |
--------------------------------------------------------------------------------
/tests/Feature/ExampleTest.php:
--------------------------------------------------------------------------------
1 | get('/');
5 |
6 | $response->assertStatus(200);
7 | });
8 |
--------------------------------------------------------------------------------
/tests/Pest.php:
--------------------------------------------------------------------------------
1 | extend(Tests\TestCase::class)
15 | ->use(Illuminate\Foundation\Testing\RefreshDatabase::class)
16 | ->in('Feature');
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Expectations
21 | |--------------------------------------------------------------------------
22 | |
23 | | When you're writing tests, you often need to check that values meet certain conditions. The
24 | | "expect()" function gives you access to a set of "expectations" methods that you can use
25 | | to assert different things. Of course, you may extend the Expectation API at any time.
26 | |
27 | */
28 |
29 | expect()->extend('toBeOne', function () {
30 | return $this->toBe(1);
31 | });
32 |
33 | /*
34 | |--------------------------------------------------------------------------
35 | | Functions
36 | |--------------------------------------------------------------------------
37 | |
38 | | While Pest is very powerful out-of-the-box, you may have some testing code specific to your
39 | | project that you don't want to repeat in every file. Here you can also expose helpers as
40 | | global functions to help you to reduce the number of lines of code in your test files.
41 | |
42 | */
43 |
44 | function something()
45 | {
46 | // ..
47 | }
48 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | toBeTrue();
5 | });
6 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import laravel from 'laravel-vite-plugin';
3 |
4 | export default defineConfig({
5 | plugins: [
6 | laravel({
7 | input: ['resources/css/app.css', 'resources/js/app.js'],
8 | refresh: true,
9 | }),
10 | ],
11 | });
12 |
--------------------------------------------------------------------------------