added created view and confirm show
CI/CD / test_and_check_app (push) Failing after 48s
CI/CD / Publish immutable image (push) Skipped

This commit is contained in:
2026-10-10 23:29:45 +02:00
parent 095ce60d8f
commit 8fadceabed
8 changed files with 151 additions and 33 deletions
+29 -5
View File
@@ -4,30 +4,54 @@ namespace App\Http\Controllers;
use App\Http\Requests\StoreOTSRequest;
use App\Models\OneTimeSecret;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
class OTSController extends Controller
{
public function created(Request $request)
{
$link = $request->session()->get('otsLink');
$request->session()->remove('otsLink');
if (! $link) {
return redirect()->route('ots.create');
}
return Inertia::render('ots/Created', [
'otsLink' => $link,
]);
}
public function store(StoreOTSRequest $request): RedirectResponse
{
$validated = $request->validated();
OneTimeSecret::create(($validated + ['expires_at' => now()->addMinutes(30)]));
$ots = OneTimeSecret::create(($validated + ['expires_at' => now()->addMinutes(30)]));
$otsLink = route('ots.show', ['oneTimeSecret' => $ots->id]);
return back();
return redirect()->route('ots.created')->with(['otsLink' => $otsLink]);
}
public function create(): Response
{
return Inertia::render('ots/Create');
return Inertia::render('ots/Create', []);
}
public function show(OneTimeSecret $oneTimeSecret): Response
{
$oneTimeSecret->delete();
return Inertia::render('ots/Show', ['oneTimeSecretId' => $oneTimeSecret->id]);
}
return Inertia::render('ots/Show', ['oneTimeSecret' => $oneTimeSecret]);
public function reveal(OneTimeSecret $oneTimeSecret): JsonResponse
{
$oneTimeSecret->delete();
if ($oneTimeSecret->expires_at->isPast()) {
abort(404);
}
return response()->json(['secret' => $oneTimeSecret->secret]);
}
}
+1 -1
View File
@@ -80,7 +80,7 @@ class TeamController extends Controller
public function create(): Response
{
return Inertia::render('team/Create');
return Inertia::render('team/Create', []);
}
/**
+27 -19
View File
@@ -203,6 +203,14 @@ settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
overrides:
brace-expansion@>=2.0.0 <2.1.5: ^2.1.5
brace-expansion@>=2.0.0 <2.1.6: ^2.1.6
shell-quote@>=1.8.4 <1.11.0: ^1.11.0
source-map-js@>=1.0.0 <1.2.2: ^1.2.2
tinypool@<2.1.2: ^2.1.2
tinypool@<=2.1.0: ^2.1.1
importers:
.:
@@ -1359,8 +1367,8 @@ packages:
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
brace-expansion@2.1.4:
resolution: {integrity: sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==}
brace-expansion@2.1.7:
resolution: {integrity: sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==}
chai@6.2.2:
resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==}
@@ -1846,8 +1854,8 @@ packages:
scheduler@0.27.0:
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
shell-quote@1.9.0:
resolution: {integrity: sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==}
shell-quote@1.12.0:
resolution: {integrity: sha512-PcByqNyT/38F2kDNi006HAMRJaULuBzq/FOsw3qdZvX/GA9W/jamDaRskgHjubHiftXK5sIFxLNkvrXUwcof6Q==}
engines: {node: '>= 0.4'}
siginfo@2.0.0:
@@ -1864,8 +1872,8 @@ packages:
resolution: {integrity: sha512-SO/3iYL5S3W57LLEniscOGPZgOqZUPCx6d3dB+52B80yJ0XstzsC/eV8gnA4tM3MHDrKz+OCFSLNjswdSC+/bA==}
engines: {node: '>=22'}
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
source-map-js@1.2.2:
resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==}
engines: {node: '>=0.10.0'}
stack-utils@2.0.6:
@@ -1923,8 +1931,8 @@ packages:
resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
engines: {node: '>=12.0.0'}
tinypool@2.1.0:
resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==}
tinypool@2.2.0:
resolution: {integrity: sha512-jBrmx4lYmaC9k/mgPbylxs7kBUxHtD8256up+HjLaDFfXScKJQyil+SWXSvhAtT7XHo+yTVlpB81PGmHP8oLSQ==}
engines: {node: ^20.0.0 || >=22.0.0}
tinyrainbow@3.1.1:
@@ -2493,7 +2501,7 @@ snapshots:
jiti: 2.7.0
lightningcss: 1.32.0
magic-string: 0.30.21
source-map-js: 1.2.1
source-map-js: 1.2.2
tailwindcss: 4.3.3
'@tailwindcss/oxide-android-arm64@4.3.3':
@@ -2735,7 +2743,7 @@ snapshots:
'@vue/shared': 3.5.42
entities: 7.0.1
estree-walker: 2.0.2
source-map-js: 1.2.1
source-map-js: 1.2.2
'@vue/compiler-dom@3.5.42':
dependencies:
@@ -2752,7 +2760,7 @@ snapshots:
estree-walker: 2.0.2
magic-string: 0.30.21
postcss: 8.5.28
source-map-js: 1.2.1
source-map-js: 1.2.2
'@vue/compiler-ssr@3.5.42':
dependencies:
@@ -2929,7 +2937,7 @@ snapshots:
balanced-match@1.0.2: {}
brace-expansion@2.1.4:
brace-expansion@2.1.7:
dependencies:
balanced-match: 1.0.2
@@ -2977,7 +2985,7 @@ snapshots:
dependencies:
chalk: 5.6.2
rxjs: 7.8.2
shell-quote: 1.9.0
shell-quote: 1.12.0
supports-color: 10.2.2
tree-kill: 1.2.2
yargs: 18.0.0
@@ -3215,7 +3223,7 @@ snapshots:
minimatch@9.0.9:
dependencies:
brace-expansion: 2.1.4
brace-expansion: 2.1.7
mrmime@2.0.1: {}
@@ -3234,7 +3242,7 @@ snapshots:
oxfmt@0.64.0(vite-plus@0.3.0(@types/node@22.20.2)(jiti@2.7.0)(typescript@5.9.3)(vite@8.2.2(@types/node@22.20.2)(jiti@2.7.0))):
dependencies:
tinypool: 2.1.0
tinypool: 2.2.0
optionalDependencies:
'@oxfmt/binding-android-arm-eabi': 0.64.0
'@oxfmt/binding-android-arm64': 0.64.0
@@ -3309,7 +3317,7 @@ snapshots:
dependencies:
nanoid: 3.3.18
picocolors: 1.1.1
source-map-js: 1.2.1
source-map-js: 1.2.2
pretty-format@27.5.1:
dependencies:
@@ -3378,7 +3386,7 @@ snapshots:
scheduler@0.27.0:
optional: true
shell-quote@1.9.0: {}
shell-quote@1.12.0: {}
siginfo@2.0.0: {}
@@ -3397,7 +3405,7 @@ snapshots:
is-fullwidth-code-point: 5.1.0
optional: true
source-map-js@1.2.1: {}
source-map-js@1.2.2: {}
stack-utils@2.0.6:
dependencies:
@@ -3447,7 +3455,7 @@ snapshots:
fdir: 6.5.0(picomatch@4.0.7)
picomatch: 4.0.7
tinypool@2.1.0: {}
tinypool@2.2.0: {}
tinyrainbow@3.1.1: {}
+8
View File
@@ -3,5 +3,13 @@ packages:
ignoreScripts: true
overrides:
brace-expansion@>=2.0.0 <2.1.5: ^2.1.5
brace-expansion@>=2.0.0 <2.1.6: ^2.1.6
shell-quote@>=1.8.4 <1.11.0: ^1.11.0
source-map-js@>=1.0.0 <1.2.2: ^1.2.2
tinypool@<2.1.2: ^2.1.2
tinypool@<=2.1.0: ^2.1.1
publicHoistPattern:
- '@inertiajs/core'
+31
View File
@@ -0,0 +1,31 @@
<script lang="ts" setup>
import { Head } from '@inertiajs/vue3';
import { Card, CardContent, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
const props = defineProps<{
otsLink: string;
}>();
const copyLink = async () => {
await navigator.clipboard.writeText(props.otsLink);
};
</script>
<template>
<div class="flex flex-col justify-center p-16">
<Head title="OTS" />
<Card class="w-full">
<CardContent>
<CardTitle class="mb-8 text-center text-2xl">
<h1>Erstellt!</h1>
</CardTitle>
<div class="flex gap-4">
<Input :model-value="otsLink" name="ots_link" readonly />
<Button variant="secondary" @click="copyLink">Kopieren</Button>
</div>
</CardContent>
</Card>
</div>
</template>
+51 -5
View File
@@ -1,19 +1,46 @@
<script lang="ts" setup>
import { Head } from '@inertiajs/vue3';
import otsRoutes from '@/routes/ots';
import { Head, useHttp } from '@inertiajs/vue3';
import type { OTS } from '@/types/models';
import { Card, CardContent } from '@/components/ui/card';
import {
Card,
CardAction,
CardContent,
CardDescription,
CardTitle,
} from '@/components/ui/card';
import { Textarea } from '@/components/ui/textarea';
import { ref } from 'vue';
import { Button } from '@/components/ui/button';
defineProps<{
oneTimeSecret: OTS;
const props = defineProps<{
oneTimeSecretId: string;
}>();
const oneTimeSecret = ref<OTS | null>(null);
const loadingSecret = ref<boolean>(false);
const confirmShow = async () => {
loadingSecret.value = true;
try {
oneTimeSecret.value = await useHttp<{}, { secret: string }>().submit(
otsRoutes.reveal({
oneTimeSecret: props.oneTimeSecretId,
}),
);
} catch (error) {
console.error(error);
} finally {
loadingSecret.value = false;
}
};
</script>
<template>
<div class="flex flex-col justify-center p-16">
<Head title="OTS" />
<Card class="w-full">
<CardContent>
<CardContent v-if="oneTimeSecret">
<Textarea
v-model="oneTimeSecret.secret"
:rows="10"
@@ -21,6 +48,25 @@ defineProps<{
readonly
/>
</CardContent>
<CardContent v-else>
<CardTitle class="text-center text-xl">
Möchtest du das Secret Anzeigen?
</CardTitle>
<CardDescription class="text-md mb-8 text-center">
Nach dem Anzeigen des Secrets wird es unwiderruflich gelöscht und kann
nicht noch einmal angezeigt werden!
</CardDescription>
<CardAction class="justify-self-center">
<Button
:disabled="loadingSecret"
class="cursor-pointer"
variant="destructive"
@click="confirmShow"
>
Anzeigen
</Button>
</CardAction>
</CardContent>
</Card>
</div>
</template>
-1
View File
@@ -8,7 +8,6 @@ export interface Team {
export interface OTS {
secret: string;
expires_at: string;
}
export type TeamMemberRole = 'owner' | 'admin' | 'member';
+2
View File
@@ -34,8 +34,10 @@ Route::middleware(['auth'])->group(function () {
Route::name('ots.')->prefix('ots')->group(function () {
Route::get('/', [OTSController::class, 'create'])->name('create');
Route::get('created', [OTSController::class, 'created'])->name('created');
Route::post('/', [OTSController::class, 'store'])->name('store');
Route::get('/{oneTimeSecret}', [OTSController::class, 'show'])->name('show');
Route::post('/{oneTimeSecret}', [OTSController::class, 'reveal'])->name('reveal');
});
require __DIR__.'/settings.php';