Skip to content

Commit

Permalink
add dashboard frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
Gregor Vostrak authored and Gregor Vostrak committed Mar 11, 2024
1 parent ace113a commit b1d4a47
Show file tree
Hide file tree
Showing 86 changed files with 5,123 additions and 848 deletions.
6 changes: 1 addition & 5 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@
require("@rushstack/eslint-patch/modern-module-resolution")

module.exports = {
extends: [
'plugin:vue/vue3-essential',
'@vue/eslint-config-typescript/recommended',
'@vue/eslint-config-prettier'
],
extends: ['plugin:vue/vue3-essential', '@vue/eslint-config-typescript/recommended', '@vue/eslint-config-prettier'],
rules: {
'vue/multi-word-component-names': 'off',
}
Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ cp .env.example .env

./vendor/bin/sail artisan migrate:fresh --seed

./vendor/bin/sail php artisan passport:install

./vendor/bin/sail npm install

./vendor/bin/sail npm run build
Expand Down Expand Up @@ -52,6 +54,19 @@ npx playwright install
npx playwright codegen solidtime.test
```

## E2E Troubleshooting

If the E2E tests are not working consistently and fail with a timeout during the authentication, you might want to delete the `test-results/.auth` directory to force new test accounts to be created.

## Generate ZOD Client

The Zodius HTTP client is generated using the following command:

```bash

npm run generate:zod
```

## Contributing

This project is in a very early stage. The structure and APIs are still subject to change and not stable.
Expand Down
10 changes: 10 additions & 0 deletions app/Http/Controllers/Api/V1/ProjectController.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ protected function checkPermission(Organization $organization, string $permissio
* Get projects
*
* @throws AuthorizationException
*
* @operationId getProjects
*/
public function index(Organization $organization): JsonResource
{
Expand All @@ -43,6 +45,8 @@ public function index(Organization $organization): JsonResource
* Get project
*
* @throws AuthorizationException
*
* @operationId getProject
*/
public function show(Organization $organization, Project $project): JsonResource
{
Expand All @@ -57,6 +61,8 @@ public function show(Organization $organization, Project $project): JsonResource
* Create project
*
* @throws AuthorizationException
*
* @operationId createProject
*/
public function store(Organization $organization, ProjectStoreRequest $request): JsonResource
{
Expand All @@ -75,6 +81,8 @@ public function store(Organization $organization, ProjectStoreRequest $request):
* Update project
*
* @throws AuthorizationException
*
* @operationId updateProject
*/
public function update(Organization $organization, Project $project, ProjectUpdateRequest $request): JsonResource
{
Expand All @@ -90,6 +98,8 @@ public function update(Organization $organization, Project $project, ProjectUpda
* Delete project
*
* @throws AuthorizationException
*
* @operationId deleteProject
*/
public function destroy(Organization $organization, Project $project): JsonResponse
{
Expand Down
8 changes: 8 additions & 0 deletions app/Http/Controllers/Api/V1/TagController.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ protected function checkPermission(Organization $organization, string $permissio
* Get tags
*
* @throws AuthorizationException
*
* @operationId getTags
*/
public function index(Organization $organization): TagCollection
{
Expand All @@ -44,6 +46,8 @@ public function index(Organization $organization): TagCollection
* Create tag
*
* @throws AuthorizationException
*
* @operationId createTag
*/
public function store(Organization $organization, TagStoreRequest $request): TagResource
{
Expand All @@ -61,6 +65,8 @@ public function store(Organization $organization, TagStoreRequest $request): Tag
* Update tag
*
* @throws AuthorizationException
*
* @operationId updateTag
*/
public function update(Organization $organization, Tag $tag, TagUpdateRequest $request): TagResource
{
Expand All @@ -76,6 +82,8 @@ public function update(Organization $organization, Tag $tag, TagUpdateRequest $r
* Delete tag
*
* @throws AuthorizationException
*
* @operationId deleteTag
*/
public function destroy(Organization $organization, Tag $tag): JsonResponse
{
Expand Down
11 changes: 10 additions & 1 deletion app/Http/Controllers/Api/V1/TimeEntryController.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ protected function checkPermission(Organization $organization, string $permissio
* Get time entries
*
* @throws AuthorizationException
*
* @operationId getTimeEntries
*/
public function index(Organization $organization, TimeEntryIndexRequest $request): JsonResource
{
Expand Down Expand Up @@ -103,6 +105,8 @@ public function index(Organization $organization, TimeEntryIndexRequest $request
* Create time entry
*
* @throws AuthorizationException|TimeEntryStillRunning
*
* @operationId createTimeEntry
*/
public function store(Organization $organization, TimeEntryStoreRequest $request): JsonResource
{
Expand All @@ -120,7 +124,7 @@ public function store(Organization $organization, TimeEntryStoreRequest $request

$timeEntry = new TimeEntry();
$timeEntry->fill($request->validated());
$timeEntry->description = $request->get('description', '');
$timeEntry->description = $request->get('description') ?? '';
$timeEntry->organization()->associate($organization);
$timeEntry->save();

Expand All @@ -131,6 +135,8 @@ public function store(Organization $organization, TimeEntryStoreRequest $request
* Update time entry
*
* @throws AuthorizationException
*
* @operationId updateTimeEntry
*/
public function update(Organization $organization, TimeEntry $timeEntry, TimeEntryUpdateRequest $request): JsonResource
{
Expand All @@ -141,6 +147,7 @@ public function update(Organization $organization, TimeEntry $timeEntry, TimeEnt
}

$timeEntry->fill($request->validated());
$timeEntry->description = $request->get('description', $timeEntry->description) ?? '';
$timeEntry->save();

return new TimeEntryResource($timeEntry);
Expand All @@ -150,6 +157,8 @@ public function update(Organization $organization, TimeEntry $timeEntry, TimeEnt
* Delete time entry
*
* @throws AuthorizationException
*
* @operationId deleteTimeEntry
*/
public function destroy(Organization $organization, TimeEntry $timeEntry): JsonResponse
{
Expand Down
2 changes: 1 addition & 1 deletion app/Http/Requests/V1/TimeEntry/TimeEntryUpdateRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public function rules(): array
],
// End of time entry (ISO 8601 format, UTC timezone)
'end' => [
'required',
'present',
'nullable',
'date', // TODO
'after:start',
Expand Down
6 changes: 3 additions & 3 deletions app/Http/Resources/V1/TimeEntry/TimeEntryResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ public function toArray(Request $request): array
/**
* @var string|null $end End of time entry (ISO 8601 format, UTC timezone, example: 2024-02-26T17:17:17Z)
*/
'end' => $this->formatDateTime($this->resource->start),
/** @var int $duration Duration of time entry in seconds */
'end' => $this->formatDateTime($this->resource->end),
/** @var int|null $duration Duration of time entry in seconds */
'duration' => $this->resource->getDuration()?->seconds,
/** @var string|null $description Description of time entry */
'description' => $this->resource->description,
Expand All @@ -42,7 +42,7 @@ public function toArray(Request $request): array
/** @var string $user_id ID of user */
'user_id' => $this->resource->user_id,
/** @var array<string> $tags List of tag IDs */
'tags' => $this->resource->tags,
'tags' => $this->resource->tags ?? [],
];
}
}
4 changes: 2 additions & 2 deletions app/Providers/JetstreamServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ protected function configurePermissions(): void
'tags:update',
'tags:delete',
'organizations:view',
])->description('Editor users have the ability to read, create, and update.');
])->description('Managers have the ability to read, create, and update their own time entries as well as those of their team.');

Jetstream::role('employee', 'Employee', [
'projects:view',
Expand All @@ -104,6 +104,6 @@ protected function configurePermissions(): void
'time-entries:update:own',
'time-entries:delete:own',
'organizations:view',
])->description('Editor users have the ability to read, create, and update.');
])->description('Employees have the ability to read, create, and update their own time entries.');
}
}
6 changes: 3 additions & 3 deletions config/scramble.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,9 @@
* ```
*/
'servers' => [
'Production' => 'https://app.solidtime.io',
'Staging' => 'https://app.staging.solidtime.io',
'Local' => 'https://soldtime.test',
'Production' => 'https://app.solidtime.io/api',
'Staging' => 'https://app.staging.solidtime.io/api',
'Local' => 'https://soldtime.test/api',
],

'middleware' => [
Expand Down
2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ services:
image: mcr.microsoft.com/playwright:v1.41.1-jammy
command: ['npx', 'playwright', 'test', '--ui-port=8080', '--ui-host=0.0.0.0']
working_dir: /src
extra_hosts:
- "solidtime.test:${REVERSE_PROXY_IP:-10.100.100.10}"
labels:
- "traefik.enable=true"
- "traefik.docker.network=${NETWORK_NAME}"
Expand Down
17 changes: 5 additions & 12 deletions e2e/auth.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,23 @@ async function registerNewUser(page, email, password) {
await page.getByLabel('Password', { exact: true }).fill(password);
await page.getByLabel('Confirm Password').fill(password);
await page.getByRole('button', { name: 'Register' }).click();
await expect(
page.getByRole('heading', { name: 'Dashboard' })
).toBeVisible();
await expect(page.getByTestId('dashboard_view')).toBeVisible();
}

test('can register, logout and log back in', async ({ page }) => {
await page.goto(PLAYWRIGHT_BASE_URL);
const email = `john+${Math.round(Math.random() * 10000)}@doe.com`;
const password = 'suchagreatpassword123';
await registerNewUser(page, email, password);
await expect(
page.getByRole('button', { name: "John's Organization" })
).toBeVisible();
await page.locator('#currentUserButton').click();

await page.getByRole('button', { name: 'Log Out' }).click();
await expect(page.getByTestId('dashboard_view')).toBeVisible();
await page.getByTestId('current_user_button').click();
await page.getByText('Log Out').click();
await page.waitForURL(PLAYWRIGHT_BASE_URL + '/');
await page.goto(PLAYWRIGHT_BASE_URL + '/login');
await page.getByLabel('Email').fill(email);
await page.getByLabel('Password').fill(password);
await page.getByRole('button', { name: 'Log in' }).click();
await expect(
page.getByRole('heading', { name: 'Dashboard' })
).toBeVisible();
await expect(page.getByTestId('dashboard_view')).toBeVisible();
});

test('can register and delete account', async ({ page }) => {
Expand Down
24 changes: 19 additions & 5 deletions e2e/organization.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,37 @@ import { PLAYWRIGHT_BASE_URL } from '../playwright/config';

async function goToOrganizationSettings(page) {
await page.goto(PLAYWRIGHT_BASE_URL + '/dashboard');
await page.locator('#currentTeamButton').click();
await page.getByRole('link', { name: 'Team Settings' }).click();
await page.getByTestId('organization_switcher').click();
await page.getByText('Team Settings').click();
}

test('test that organization name can be updated', async ({ page }) => {
await goToOrganizationSettings(page);
await page.getByLabel('Team Name').fill('NEW ORG NAME');
await page.getByLabel('Team Name').press('Enter');
await page.getByLabel('Team Name').press('Meta+r');
await expect(page.getByRole('navigation')).toContainText('NEW ORG NAME');
await expect(page.getByTestId('organization_switcher')).toContainText(
'NEW ORG NAME'
);
});

test('test that new manager can be invited', async ({ page }) => {
await goToOrganizationSettings(page);
const editorId = Math.round(Math.random() * 10000);
await page.getByLabel('Email').fill(`new+${editorId}@editor.test`);
await page.getByRole('button', { name: 'Manager' }).click();
await page.getByRole('button', { name: 'Add' }).click();
await page.reload();
await expect(page.getByRole('main')).toContainText(
`new+${editorId}@editor.test`
);
});

test('test that new editor can be invited', async ({ page }) => {
test('test that new employee can be invited', async ({ page }) => {
await goToOrganizationSettings(page);
const editorId = Math.round(Math.random() * 10000);
await page.getByLabel('Email').fill(`new+${editorId}@editor.test`);
await page.getByRole('button', { name: 'Editor' }).click();
await page.getByRole('button', { name: 'Employee' }).click();
await page.getByRole('button', { name: 'Add' }).click();
await page.reload();
await expect(page.getByRole('main')).toContainText(
Expand Down
Loading

0 comments on commit b1d4a47

Please sign in to comment.