Initial commit

This commit is contained in:
Ronald Huynen
2026-03-23 21:37:59 +01:00
commit 2547717edb
2193 changed files with 972171 additions and 0 deletions

View File

@@ -0,0 +1,118 @@
@props([
'result' => [],
'index' => 0,
'href' => null,
'wireClick' => null,
'showScore' => false,
'showCallable' => true,
'showReactions' => true,
'photoBlur' => 0,
'photoContrast' => 100,
'photoSaturate' => 100,
'photoBrightness' => 100,
'heightClass' => 'h-[430px] md:h-[550px] lg:h-[430px]',
'truncateExcerpt' => false,
])
<div class="relative flex flex-col justify-end overflow-hidden rounded-lg bg-theme-background shadow-2xl {{ $heightClass }}">
@if ($wireClick)
<a href="#" wire:click.prevent="{{ $wireClick }}"
class="relative flex h-full cursor-pointer flex-col overflow-hidden rounded-lg">
@else
<a href="{{ $href ?? route('call.show', ['id' => $result['id']]) }}"
class="relative flex h-full cursor-pointer flex-col overflow-hidden rounded-lg">
@endif
<!-- Tag category color background -->
<div class="absolute inset-0 z-0 bg-{{ $result['tag_color'] ?? 'gray' }}-400"></div>
<div class="absolute inset-0 z-10 bg-black/50"></div>
<!-- Card content -->
<div class="relative z-20 flex h-full flex-col justify-between px-6 pt-10 pb-10 lg:px-14 lg:pt-14 lg:pb-14 text-white">
<div class="flex-1">
<div class="flex items-start justify-between gap-4">
<div class="flex flex-wrap items-center gap-2">
{{-- Deepest tag category badge only --}}
@php $deepestCat = !empty($result['tag_categories']) ? last($result['tag_categories']) : null; @endphp
@if ($deepestCat)
<span class="bg-{{ $deepestCat['color'] ?? 'gray' }}-400 inline-flex items-center rounded-md px-2 py-1 text-sm font-normal text-black">
{{ $deepestCat['name'] }}
</span>
@endif
</div>
{{-- Reaction button --}}
@if ($showReactions)
<div class="flex items-start -m-2 flex-shrink-0">
<div class="-my-3">
@livewire('reaction-button', [
'typeName' => 'like',
'showCounter' => true,
'reactionCounter' => $result['like_count'],
'modelClass' => $result['model'],
'modelId' => $result['id'],
'size' => 'w-6 h-6',
'inverseColors' => true,
], key('like-' . $result['model'] . '-' . $result['id'] . '-' . $index))
</div>
</div>
@endif
</div>
<div>
<h2 class="my-3 text-4xl font-semibold leading-tight line-clamp-2">
{{ $result['title'] }}
</h2>
{{-- Location + expiry badges --}}
@if (!empty($result['location']) || !empty($result['expiry_badge_text']))
<div class="mt-3 flex flex-wrap items-center gap-2">
@if (!empty($result['location']))
<h4 class="inline-block items-center rounded-sm bg-black px-2 pb-1 pt-0.5 text-sm font-normal uppercase text-white">
{{ $result['location'] }}
</h4>
@endif
@if (!empty($result['expiry_badge_text']))
<h4 class="inline-block items-center rounded-sm bg-black px-2 pb-1 pt-0.5 text-sm font-normal uppercase text-white">
{{ $result['expiry_badge_text'] }}
</h4>
@endif
</div>
@endif
<p class="mt-3 text-base font-semibold {{ $truncateExcerpt ? 'line-clamp-2' : '' }}">
{{ $result['excerpt'] }}
</p>
</div>
</div>
@if ($showCallable)
<div class="flex flex-wrap items-end gap-4">
<div class="flex items-center gap-3">
{{-- Callable avatar --}}
@if (!empty($result['photo']))
<div class="h-16 w-16 flex-shrink-0 rounded-full outline outline-1 outline-offset-1 outline-white/50 overflow-hidden">
<img src="{{ $result['photo'] }}"
alt="{{ $result['callable_name'] }}"
class="h-full w-full object-cover" />
</div>
@endif
<div class="flex flex-col gap-1 text-sm">
{{-- Callable name --}}
@if (!empty($result['callable_name']))
<span class="font-medium">{{ $result['callable_name'] }}</span>
@endif
{{-- Callable location --}}
@if (!empty($result['callable_location']))
<span class="text-white/80">{{ $result['callable_location'] }}</span>
@endif
</div>
</div>
</div>
@endif
</div>
@if ($showScore)
<div class="text-2xs absolute bottom-5 right-5 z-20 text-transparent sm:text-white drop-shadow">
{{ __('search score') . ': ' . round($result['score'] ?? 0, 1) }}
</div>
@endif
</a>
</div>

View File

@@ -0,0 +1,65 @@
<footer class="bg-theme-brand mx-auto pt-4 lg:pt-9">
<div class="container mx-auto max-w-7xl px-6 py-9 sm:px-6 lg:px-8">
<div class="grid grid-cols-2 gap-y-12 gap-x-8 md:gap-12 text-base md:grid-cols-3 lg:grid-cols-4">
@php
$footerSections = collect(timebank_config('footer.sections', []))
->where('visible', true)
->sortBy('order');
@endphp
@foreach($footerSections as $section)
<div>
<p class="text-sm font-extrabold text-theme-background md:font-semibold dark:text-theme-background">
{{ __($section['title']) }}
</p>
<div class="mt-1 flex flex-col items-start space-y-2 text-xs">
@php
$links = collect($section['links'] ?? [])
->where('visible', true)
->sortBy('order');
@endphp
@foreach($links as $link)
@if(isset($link['auth_required']) && $link['auth_required'])
@auth
<a class="text-theme-light hover:text-theme-accent dark:text-theme-secondary dark:hover:text-theme-accent transition-colors duration-300 hover:underline"
href="{{ route($link['route']) }}">
{{ __($link['title']) }}
</a>
@endauth
@elseif(isset($link['url']))
<a class="text-theme-light hover:text-theme-accent dark:text-theme-secondary dark:hover:text-theme-accent transition-colors duration-300 hover:underline"
href="{{ $link['url'] }}">
{{ __($link['title']) }}
</a>
@else
<a class="text-theme-light hover:text-theme-accent dark:text-theme-secondary dark:hover:text-theme-accent transition-colors duration-300 hover:underline"
href="{{ route($link['route']) }}">
{{ __($link['title']) }}
</a>
@endif
@endforeach
</div>
</div>
@endforeach
</div>
<!-- Bottom Section: Application Mark and Text -->
<div class="my-6 flex w-full flex-col items-end justify-end space-y-2">
<div class="invert-100 flex flex-col items-center justify-center">
<a href="{{ route('main') }}">
<x-jetstream.application-mark class="block h-9 w-auto" />
</a>
<p class="mt-2 text-2xs dark:text-theme-light font-semibold text-gray-800">
{{ __(timebank_config('footer.tagline','')) }}
</p>
</div>
</div>
</div>
</footer>

View File

@@ -0,0 +1,6 @@
<button {{ $attributes->merge([
'type' => 'button',
'class' => 'inline-flex items-center pl-4 pr-2 py-2 bg-theme-background border border-theme-border rounded-md font-semibold text-xs text-theme-primary uppercase tracking-widest shadow-sm hover:bg-theme-border hover:text-theme-secondary focus:outline-none focus:border-blue-300 focus:ring focus:ring-blue-200 active:text-theme-primary active:bg-theme-surface disabled:opacity-25 transition']) }}>
{{ $slot }}
<span class="ml-2"><x-icon name="chevron-down" class="w-5 h-5" mini/></span>
</button>

View File

@@ -0,0 +1,10 @@
@props(['on'])
<div x-data="{ shown: false, timeout: null }"
x-init="@this.on('{{ $on }}', () => { clearTimeout(timeout); shown = true; timeout = setTimeout(() => { shown = false }, 2000); })"
x-show.transition.out.opacity.duration.1500ms="shown"
x-transition:leave.opacity.duration.1500ms
style="display: none;"
{{ $attributes->merge(['class' => 'text-sm text-theme-secondary']) }}>
{!! $slot->isEmpty() ? 'Saved.' : $slot !!}
</div>

View File

@@ -0,0 +1,12 @@
<div {{ $attributes->merge(['class' => 'md:grid md:grid-cols-3 md:gap-6']) }}>
<x-jetstream.section-title>
<x-slot name="title">{{ $title }}</x-slot>
<x-slot name="description">{{ $description }}</x-slot>
</x-jetstream.section-title>
<div class="md:mt-0 md:col-span-2">
<div class="px-4 py-5 sm:p-6 bg-theme-background shadow sm:rounded-lg">
{{ $content }}
</div>
</div>
</div>

View File

@@ -0,0 +1,4 @@
<div class="w-full max-w-md mx-auto">
@include(theme_logo('svg_inline', 'logos.timebank_cc'), ['logoTitle' => $logoTitle ?? __('Search') . ' - ' . config('app.name')])
</div>

View File

@@ -0,0 +1,4 @@
<div {{ $attributes }}>
@include(theme_logo('svg_inline', 'logos.timebank_cc'), ['logoTitle' => $logoTitle ?? config('app.name')])
</div>

View File

@@ -0,0 +1,6 @@
<a href="/" class="block max-w-md">
<div class="w-full h-auto">
@include(theme_logo('svg_inline', 'logos.timebank_cc'), ['logoTitle' => $logoTitle ?? __('Go to homepage') . ' - ' . config('app.name')])
</div>
</a>

View File

@@ -0,0 +1,14 @@
<div class="min-h-screen flex flex-col justify-center items-center py-6 relative overflow-hidden">
<!-- Background Image -->
<div class="absolute inset-0 z-0">
@livewire('welcome.login-landing-post', ['type' => 'SiteContents\Welcome\Landing' ?? null, 'random' => true, 'limit' => 1])
</div>
<!-- Login Modal Card -->
<div class="mx-8 sm:max-w-md px-12 py-6 bg-theme-background dark:bg-theme-secondary backdrop-blur-sm shadow-2xl rounded-lg z-20 relative">
<div class="flex justify-center mb-8">
{{ $logo }}
</div>
{{ $slot }}
</div>
</div>

View File

@@ -0,0 +1,46 @@
@props(['style' => session('flash.bannerStyle', 'success'), 'message' => session('flash.banner')])
<div x-data="{{ json_encode(['show' => true, 'style' => $style, 'message' => $message]) }}"
:class="{ 'bg-green-100': style == 'success', 'bg-red-100': style == 'danger', 'bg-theme-surface': style != 'success' && style != 'danger' }"
style="display: none;"
x-show="show && message"
x-init="
document.addEventListener('banner-message', event => {
style = event.detail.style;
message = event.detail.message;
show = true;
});
">
<div class="mx-auto py-2 px-3 sm:px-6 lg:px-8">
<div class="flex items-center justify-between flex-wrap">
<div class="w-0 flex-1 flex items-center min-w-0">
<span class="flex p-2 rounded-lg" :class="{ 'bg-green-100': style == 'success', 'bg-red-100': style == 'danger' }">
<svg x-show="style == 'success'" class="h-5 w-5 text-green-700" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<svg x-show="style == 'danger'" class="h-5 w-5 text-red-700" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<svg x-show="style != 'success' && style != 'danger'" class="h-5 w-5 text-dark" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</span>
<p class="ml-3 font-medium text-sm text-dark truncate" x-text="message"></p>
</div>
<div class="shrink-0 sm:ml-3">
<button
type="button"
class="-mr-1 flex p-2 rounded-md focus:outline-none sm:-mr-2 transition"
:class="{ 'hover:bg-green-300 focus:bg-green-300': style == 'success', 'hover:bg-red-300 focus:bg-red-300': style == 'danger' }"
aria-label="Dismiss"
x-on:click="show = false">
<svg class="h-5 w-5 text-dark" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,37 @@
@php
// Determine the wire target from wire:click or use explicit wire:target if provided
$wireTarget = null;
if ($attributes->has('wire:target')) {
$wireTarget = $attributes->get('wire:target');
} else {
// Look for wire:click variants (wire:click, wire:click.prevent, etc.)
foreach ($attributes->getAttributes() as $key => $value) {
if (str_starts_with($key, 'wire:click')) {
// Skip $dispatch, $set, $toggle etc. as they don't create loading states
if (!str_starts_with(trim($value), '$')) {
$wireTarget = $value;
break;
}
}
}
}
@endphp
<button {{ $attributes->merge(['type' => 'submit', 'class' => 'inline-flex items-center justify-center px-4 py-2 bg-theme-primary-900 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest shadow-lg hover:shadow-sm hover:bg-theme-primary-700 hover:text-theme-primary-100 focus:outline-none focus:border-theme-accent focus:ring-1 focus:ring-theme-accent active:bg-theme-primary-900 disabled:opacity-25 transition']) }}>
@if(!$attributes->has('no-spinner') && $wireTarget)
<span class="relative flex items-center justify-center w-full">
<span wire:loading wire:target="{{ $wireTarget }}" class="absolute flex items-center justify-center">
<svg class="animate-spin h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</span>
<span wire:loading.class="opacity-0" wire:target="{{ $wireTarget }}" class="inline-flex items-center">
{{ $slot }}
</span>
</span>
@else
{{ $slot }}
@endif
</button>

View File

@@ -0,0 +1 @@
<input type="checkbox" {!! $attributes->merge(['class' => 'rounded border-theme-border text-theme-secondary shadow-sm focus:border-theme-border focus:ring focus:ring-gray-200 focus:ring-opacity-50']) !!}>

View File

@@ -0,0 +1,27 @@
@props(['id' => null, 'maxWidth' => null])
<x-jetstream.modal :id="$id" :maxWidth="$maxWidth" {{ $attributes }}>
<div class="bg-theme-background px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div class="mx-auto shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<svg class="h-6 w-6 text-red-600" stroke="currentColor" fill="none" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
</svg>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg">
{!! $title !!}
</h3>
<div class="mt-2">
{!! $content !!}
</div>
</div>
</div>
</div>
<div class="flex flex-row justify-end px-6 py-4 bg-theme-surface text-right">
{!! $footer !!}
</div>
</x-jetstream.modal>

View File

@@ -0,0 +1,46 @@
@props(['title' => __('Confirm Password'), 'content' => __('For your security, please confirm your password to continue.'), 'button' => __('Confirm')])
@php
$confirmableId = md5($attributes->wire('then'));
@endphp
<span
{{ $attributes->wire('then') }}
x-data
x-ref="span"
x-on:click="$wire.startConfirmingPassword('{{ $confirmableId }}')"
x-on:password-confirmed.window="setTimeout(() => $event.detail.id === '{{ $confirmableId }}' && $refs.span.dispatchEvent(new CustomEvent('then', { bubbles: false })), 250);"
>
{{ $slot }}
</span>
@once
<x-jetstream.dialog-modal wire:model.live="confirmingPassword">
<x-slot name="title">
{{ $title }}
</x-slot>
<x-slot name="content">
{{ $content }}
<div class="mt-4" x-data="{}" x-on:confirming-password.window="setTimeout(() => $refs.confirmable_password.focus(), 250)">
<x-jetstream.input type="password" class="mt-1 block w-3/4" placeholder="{{ __('Password') }}"
x-ref="confirmable_password"
wire:model="confirmablePassword"
wire:keydown.enter="confirmPassword" />
<x-jetstream.input-error for="confirmable_password" class="mt-2" />
</div>
</x-slot>
<x-slot name="footer">
<x-jetstream.secondary-button wire:click="stopConfirmingPassword" wire:loading.attr="disabled">
{{ __('Cancel') }}
</x-jetstream.secondary-button>
<x-jetstream.button class="ml-3" dusk="confirm-password-button" wire:click="confirmPassword" wire:loading.attr="disabled">
{{ $button }}
</x-jetstream.button>
</x-slot>
</x-jetstream.dialog-modal>
@endonce

View File

@@ -0,0 +1,37 @@
@php
// Determine the wire target from wire:click or use explicit wire:target if provided
$wireTarget = null;
if ($attributes->has('wire:target')) {
$wireTarget = $attributes->get('wire:target');
} else {
// Look for wire:click variants (wire:click, wire:click.prevent, etc.)
foreach ($attributes->getAttributes() as $key => $value) {
if (str_starts_with($key, 'wire:click')) {
// Skip $dispatch, $set, $toggle etc. as they don't create loading states
if (!str_starts_with(trim($value), '$')) {
$wireTarget = $value;
break;
}
}
}
}
@endphp
<button {{ $attributes->merge(['type' => 'button', 'class' => 'inline-flex items-center justify-center px-4 py-2 bg-theme-danger-dark border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest shadow-md hover:shadow-sm hover:bg-theme-danger focus:outline-none focus:border-theme-accent focus:ring-1 focus:ring-theme-accent active:bg-theme-danger disabled:opacity-25 transition']) }}>
@if(!$attributes->has('no-spinner') && $wireTarget)
<span class="relative flex items-center justify-center w-full">
<span wire:loading wire:target="{{ $wireTarget }}" class="absolute flex items-center justify-center">
<svg class="animate-spin h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</span>
<span wire:loading.class="opacity-0" wire:target="{{ $wireTarget }}" class="inline-flex items-center">
{{ $slot }}
</span>
</span>
@else
{{ $slot }}
@endif
</button>

View File

@@ -0,0 +1,24 @@
@props(['id' => null, 'maxWidth' => null, 'closeButton' => false])
<x-jetstream.modal :id="$id" :maxWidth="$maxWidth" {{ $attributes }}>
<div class="px-6 py-6">
<div class="flex items-center justify-between">
<div class="text-xl font-medium">
{!! $title !!}
</div>
@if($closeButton)
<button type="button" @click="show = false" class="hover:text-theme-secondary transition-colors">
<x-icon name="x-mark" class="w-6 h-6" />
</button>
@endif
</div>
<div class="m-6">
{!! $content !!}
</div>
<div class="flex flex-row justify-end px-6 py-6 text-right">
{!! $footer !!}
</div>
</div>
</x-jetstream.modal>

View File

@@ -0,0 +1 @@
<a {{ $attributes->merge(['class' => 'block px-4 py-2 text-sm leading-5 text-theme-primary hover:bg-theme-surface focus:outline-none focus:bg-theme-surface transition']) }}>{{ $slot }}</a>

View File

@@ -0,0 +1,47 @@
@props(['align' => 'right', 'width' => '48', 'contentClasses' => 'py-1 bg-theme-background', 'dropdownClasses' => ''])
@php
switch ($align) {
case 'left':
$alignmentClasses = 'origin-top-left left-0';
break;
case 'top':
$alignmentClasses = 'origin-top';
break;
case 'none':
case 'false':
$alignmentClasses = '';
break;
case 'right':
default:
$alignmentClasses = 'origin-top-right right-0';
break;
}
switch ($width) {
case '48':
$width = 'w-48';
break;
}
@endphp
<div class="relative" x-data="{ open: false }" @click.away="open = false" @close.stop="open = false">
<div @click="open = ! open">
{{ $trigger }}
</div>
<div x-show="open"
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="transform opacity-0 scale-95"
x-transition:enter-end="transform opacity-100 scale-100"
x-transition:leave="transition ease-in duration-75"
x-transition:leave-start="transform opacity-100 scale-100"
x-transition:leave-end="transform opacity-0 scale-95"
class="absolute z-50 mt-2 {{ $width }} rounded-md shadow-lg {{ $alignmentClasses }} {{ $dropdownClasses }}"
style="display: none;"
@click="open = false">
<div class="rounded-md ring-1 ring-black ring-opacity-5 {{ $contentClasses }}">
{{ $content }}
</div>
</div>
</div>

View File

@@ -0,0 +1,25 @@
@props(['submit'])
<div {{ $attributes->merge(['class' => 'md:grid md:grid-cols-3 md:gap-6']) }}>
<div class="mt-6 mb-6 md:mt-0 md:mb-0">
<x-jetstream.section-title>
<x-slot name="title">{{ $title }}</x-slot>
<x-slot name="description">{{ $description }}</x-slot>
</x-jetstream.section-title>
</div>
<div class="md:mt-0 md:col-span-2">
<form wire:submit="{{ $submit }}">
<div class="px-4 py-5 bg-theme-background sm:p-6 shadow sm:rounded-md">
<div class="grid grid-cols-6 gap-6">
{{ $form }}
</div>
@if (isset($actions))
<div class="flex items-center justify-end px-4 py-3 bg-gray-white text-right">
{{ $actions }}
</div>
@endif
</div>
</form>
</div>
</div>

View File

@@ -0,0 +1,5 @@
@props(['for'])
@error($for)
<p {{ $attributes->merge(['class' => 'text-sm text-red-600']) }}>{{ $message }}</p>
@enderror

View File

@@ -0,0 +1,3 @@
@props(['disabled' => false])
<input {{ $disabled ? 'disabled' : '' }} {!! $attributes->merge(['class' => 'border-theme-border focus:border-theme-accent focus:ring-1 focus:ring-theme-accent rounded-md shadow-sm']) !!}>

View File

@@ -0,0 +1,5 @@
@props(['value'])
<label {{ $attributes->merge(['class' => 'block font-medium text-sm text-theme-primary']) }}>
{!! html_entity_decode($value ?? $slot, ENT_QUOTES | ENT_HTML5, 'UTF-8') !!}
</label>

View File

@@ -0,0 +1,7 @@
<button {{ $attributes->merge([
'type' => 'button',
'class' => 'inline-flex items-center px-4 py-2 bg-theme-surface border border-theme-border rounded-md font-semibold text-xs text-theme-primary uppercase tracking-widest shadow-md hover:shadow-sm hover:bg-theme-primary-50 hover:text-theme-secondary focus:outline-none focus:border-theme-accent focus:ring-1 focus:ring-theme-accent active:text-theme-primary active:bg-theme-surface disabled:opacity-25 transition']) }}>
{{ $slot }}
</button>

View File

@@ -0,0 +1,73 @@
@props(['id', 'maxWidth'])
@php
$id = $id ?? md5($attributes->wire('model'));
$maxWidth = [
'sm' => 'sm:max-w-sm',
'md' => 'sm:max-w-md',
'lg' => 'sm:max-w-lg',
'xl' => 'sm:max-w-xl',
'2xl' => 'sm:max-w-2xl',
'3xl' => 'sm:max-w-3xl',
'4xl' => 'sm:max-w-4xl',
][$maxWidth ?? '2xl'];
@endphp
<div
x-data="{
show: @entangle($attributes->wire('model')),
focusables() {
// All focusable element types...
let selector = 'a, button, input:not([type=\'hidden\']), textarea, select, details, [tabindex]:not([tabindex=\'-1\'])'
return [...$el.querySelectorAll(selector)]
// All non-disabled elements...
.filter(el => ! el.hasAttribute('disabled'))
},
firstFocusable() { return this.focusables()[0] },
lastFocusable() { return this.focusables().slice(-1)[0] },
nextFocusable() { return this.focusables()[this.nextFocusableIndex()] || this.firstFocusable() },
prevFocusable() { return this.focusables()[this.prevFocusableIndex()] || this.lastFocusable() },
nextFocusableIndex() { return (this.focusables().indexOf(document.activeElement) + 1) % (this.focusables().length + 1) },
prevFocusableIndex() { return Math.max(0, this.focusables().indexOf(document.activeElement)) -1 },
}"
x-init="$watch('show', value => {
if (value) {
document.body.classList.add('overflow-y-hidden');
{{ $attributes->has('focusable') ? 'setTimeout(() => firstFocusable().focus(), 100)' : '' }}
} else {
document.body.classList.remove('overflow-y-hidden');
@this.dispatch('closeModal');
}
})"
x-on:close.stop="show = false"
x-on:keydown.escape.window="show = false"
x-on:keydown.tab.prevent="$event.shiftKey || nextFocusable().focus()"
x-on:keydown.shift.tab.prevent="prevFocusable().focus()"
x-show="show"
id="{{ $id }}"
class="jetstream-modal fixed inset-0 overflow-y-auto px-4 py-6 sm:px-0 z-50"
style="display: none;"
>
<div x-show="show" class="fixed inset-0 transform transition-all" {{--x-on:click="show = false"--}} x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
>
<div class="absolute inset-0 bg-black opacity-50"></div>
</div>
<div x-show="show" class="mb-6 bg-theme-background rounded-lg overflow-hidden shadow-xl transform transition-all sm:w-full {{ $maxWidth }} sm:mx-auto"
x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95">
{{ $slot }}
</div>
</div>

View File

@@ -0,0 +1,11 @@
@props(['active'])
@php
$classes = ($active ?? false)
? 'inline-flex items-center px-1 pt-1 border-theme-muted text-sm font-medium leading-5 text-theme-primary hover:text-theme-secondary focus:outline-none focus:border-gray-900'
: 'inline-flex items-center px-1 pt-1 border-transparent text-sm font-medium leading-5 text-theme-primary hover:text-theme-secondary hover:border-theme-border focus:outline-none focus:text-theme-primary focus:border-theme-border transition';
@endphp
<a {{ $attributes->merge(['class' => $classes]) }}>
{{ $slot }}
</a>

View File

@@ -0,0 +1,11 @@
@props(['active'])
@php
$classes = ($active ?? false)
? 'block pl-3 pr-4 py-2 border-l-4 border-black text-base font-semibold text-theme-primary bg-theme-surface focus:outline-none focus:text-theme-secondary focus:bg-theme-border focus:border-gray-700'
: 'block pl-3 pr-4 py-2 border-l-4 border-transparent text-base text-theme-primary leading-5 hover:text-theme-primary hover:bg-theme-surface hover:border-theme-border focus:outline-none focus:text-theme-primary focus:bg-theme-surface focus:border-theme-border';
@endphp
<a {{ $attributes->merge(['class' => $classes]) }}>
{{ $slot }}
</a>

View File

@@ -0,0 +1,41 @@
@php
// Determine the wire target from wire:click or use explicit wire:target if provided
$wireTarget = null;
if ($attributes->has('wire:target')) {
$wireTarget = $attributes->get('wire:target');
} else {
// Look for wire:click variants (wire:click, wire:click.prevent, etc.)
foreach ($attributes->getAttributes() as $key => $value) {
if (str_starts_with($key, 'wire:click')) {
// Skip $dispatch, $set, $toggle etc. as they don't create loading states
if (!str_starts_with(trim($value), '$')) {
$wireTarget = $value;
break;
}
}
}
}
@endphp
<button {{ $attributes->merge([
'type' => 'button',
'class' => 'inline-flex items-center px-4 py-2 bg-theme-secondary border border-theme-border rounded-md font-semibold text-xs text-theme-primary uppercase tracking-widest shadow-md hover:shadow-sm hover:bg-theme-border hover:text-theme-secondary focus:outline-none focus:border-theme-accent focus:ring-1 focus:ring-theme-accent active:text-theme-primary active:bg-theme-surface disabled:opacity-25 transition']) }}>
@if(!$attributes->has('no-spinner') && $wireTarget)
<span class="relative flex items-center justify-center w-full">
<span wire:loading wire:target="{{ $wireTarget }}" class="absolute flex items-center justify-center">
<svg class="animate-spin h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</span>
<span wire:loading.class="opacity-0" wire:target="{{ $wireTarget }}" class="inline-flex items-center">
{{ $slot }}
</span>
</span>
@else
{{ $slot }}
@endif
</button>

View File

@@ -0,0 +1,4 @@
<div class="hidden sm:block">
<div class="py-8">
</div>
</div>

View File

@@ -0,0 +1,13 @@
<div class="md:col-span-1 flex justify-between">
<div class="px-4 sm:px-0">
<h3 class="text-xl font-strong text-theme-primary">{!! $title !!}</h3>
<p class="hidden md:block mt-1 text-sm text-theme-secondary">
{!! $description !!}
</p>
</div>
<div class="px-4 sm:px-0">
{!! $aside ?? '' !!}
</div>
</div>

View File

@@ -0,0 +1,19 @@
@props(['team', 'component' => 'dropdown-link'])
<form method="POST" action="{{ route('current-team.update') }}" x-data>
@method('PUT')
@csrf
<!-- Hidden Team ID -->
<input type="hidden" name="team_id" value="{{ $team->id }}">
<x-dynamic-component :component="$component" href="#" x-on:click.prevent="$root.submit();">
<div class="flex items-center">
@if (Auth::user()->isCurrentTeam($team))
<svg class="mr-2 h-5 w-5 text-green-400" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" stroke="currentColor" viewBox="0 0 24 24"><path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
@endif
<div class="truncate">{{ $team->name }}</div>
</div>
</x-dynamic-component>
</form>

View File

@@ -0,0 +1,51 @@
@props(['style' => session('flash.bannerStyle', 'success'), 'message' => session('flash.banner')])
<div x-data="{{ json_encode(['show' => true, 'style' => $style, 'message' => $message]) }}"
:class="{ 'fixed bottom-0 left-0 right-0 bg-green-100': style == 'success', 'fixed bottom-0 left-0 right-0 bg-red-100': style == 'danger', 'fixed bottom-0 left-0 right-0 bg-theme-surface': style != 'success' && style != 'danger' }"
style="display: none;"
x-show="show && message"
x-transition:leave.opacity.duration.1000ms
x-init="
document.addEventListener('toaster-message', event => {
style = event.detail.style;
message = event.detail.message;
show = true;
timeout = setTimeout(() => { show = false }, 4000);
});
">
<div class="mx-auto py-4 px-6 sm:px-6 lg:px-8">
<div class="flex items-center justify-between flex-wrap">
<div class="w-0 flex-1 flex items-center min-w-0">
<span class="flex p-2 rounded-lg" :class="{ 'bg-green-100': style == 'success', 'bg-red-100': style == 'danger' }">
<svg x-show="style == 'success'" class="h-5 w-5 text-green-700" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<svg x-show="style == 'danger'" class="h-5 w-5 text-red-700" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<svg x-show="style != 'success' && style != 'danger'" class="h-5 w-5 text-dark" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</span>
<p class="ml-3 font-medium text-sm text-dark truncate" x-text="message"></p>
</div>
<div class="shrink-0 sm:ml-3">
<button
type="button"
class="-mr-1 flex p-2 rounded-md focus:outline-none sm:-mr-2 transition"
:class="{ 'hover:bg-green-300 focus:bg-green-300': style == 'success', 'hover:bg-red-300 focus:bg-red-300': style == 'danger' }"
aria-label="Dismiss"
x-on:click="show = false">
<svg class="h-5 w-5 text-dark" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,11 @@
@if ($errors->any())
<div {{ $attributes }}>
<div class="font-medium text-red-600">{{ __('Whoops! Something went wrong.') }}</div>
<ul class="mt-3 list-disc list-inside text-sm text-red-600">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif

View File

@@ -0,0 +1,93 @@
<div class="p-6 sm:px-20 bg-theme-background border-b border-theme-border">
<div>
<x-jetstream.application-logo class="block h-12 w-auto" />
</div>
<div>
{{-- {{ __('Last login at')}} {{ $loginAt }} --}}
</div>
<div class="mt-8 text-2xl">
Welcome to your Jetstream application!
</div>
<div class="mt-6 text-theme-muted">
Laravel Jetstream provides a beautiful, robust starting point for your next Laravel application. Laravel is designed
to help you build your application using a development environment that is simple, powerful, and enjoyable. We believe
you should love expressing your creativity through programming, so we have spent time carefully crafting the Laravel
ecosystem to be a breath of fresh air. We hope you love it.
</div>
</div>
<div class="bg-theme-border bg-opacity-25 grid grid-cols-1 md:grid-cols-2">
<div class="p-6">
<div class="flex items-center">
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" class="w-8 h-8 text-theme-muted"><path d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"></path></svg>
<div class="ml-4 text-lg text-theme-secondary leading-7 font-semibold"><a href="https://laravel.com/docs">Documentation</a></div>
</div>
<div class="ml-12">
<div class="mt-2 text-sm text-theme-muted">
Laravel has wonderful documentation covering every aspect of the framework. Whether you're new to the framework or have previous experience, we recommend reading all of the documentation from beginning to end.
</div>
<a href="https://laravel.com/docs">
<div class="mt-3 flex items-center text-sm font-semibold text-theme-primary">
<div>Explore the documentation</div>
<div class="ml-1 text-theme-muted">
<svg viewBox="0 0 20 20" fill="currentColor" class="w-4 h-4"><path fill-rule="evenodd" d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg>
</div>
</div>
</a>
</div>
</div>
<div class="p-6 border-t border-theme-border md:border-t-0 md:border-l">
<div class="flex items-center">
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" class="w-8 h-8 text-theme-muted"><path d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"></path><path d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>
<div class="ml-4 text-lg text-theme-secondary leading-7 font-semibold"><a href="https://laracasts.com">Laracasts</a></div>
</div>
<div class="ml-12">
<div class="mt-2 text-sm text-theme-muted">
Laracasts offers thousands of video tutorials on Laravel, PHP, and JavaScript development. Check them out, see for yourself, and massively level up your development skills in the process.
</div>
<a href="https://laracasts.com">
<div class="mt-3 flex items-center text-sm font-semibold text-theme-primary">
<div>Start watching Laracasts</div>
<div class="ml-1 text-theme-muted">
<svg viewBox="0 0 20 20" fill="currentColor" class="w-4 h-4"><path fill-rule="evenodd" d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg>
</div>
</div>
</a>
</div>
</div>
<div class="p-6 border-t border-theme-border">
<div class="flex items-center">
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" class="w-8 h-8 text-theme-muted"><path d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path></svg>
<div class="ml-4 text-lg text-theme-secondary leading-7 font-semibold"><a href="https://tailwindcss.com/">Tailwind</a></div>
</div>
<div class="ml-12">
<div class="mt-2 text-sm text-theme-muted">
Laravel Jetstream is built with Tailwind, an amazing utility first CSS framework that doesn't get in your way. You'll be amazed how easily you can build and maintain fresh, modern designs with this wonderful framework at your fingertips.
</div>
</div>
</div>
<div class="p-6 border-t border-theme-border md:border-l">
<div class="flex items-center">
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" class="w-8 h-8 text-theme-muted"><path d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"></path></svg>
<div class="ml-4 text-lg text-theme-secondary leading-7 font-semibold">Authentication</div>
</div>
<div class="ml-12">
<div class="mt-2 text-sm text-theme-muted">
Authentication and registration views are included with Laravel Jetstream, as well as support for user email verification and resetting forgotten passwords. So, you're free to get started what matters most: building your application.
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,74 @@
<nav id="navigation-menu" class="bg-theme-background shadow-md border-b border-theme-primary fixed top-0 left-0 right-0 z-50" x-data="{ open: false }">
@if(auth()->guest() || request()->routeIs('goodbye-deleted-user'))
<!-- Primary Navigation Menu -->
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div class="flex h-16 items-center justify-between">
<!-- Logo -->
<div class="flex shrink-0 items-center">
<a href="{{ route('welcome') }}">
<x-jetstream.application-mark class="block h-9 w-auto" />
</a>
</div>
<!-- Language selector ---->
<!-- This changes the session('locale') and by the Middleware StoreUserLangPreference this locale
is stored as the lang_preference in the user table -->
@php
$languages = Illuminate\Support\Facades\DB::table('languages')->orderBy('lang_code', 'asc')->get();
$supportedLocales = LaravelLocalization::getSupportedLocales();
// Sort the supported locales alphabetically by the language name
uasort($supportedLocales, function ($a, $b) {
return strcmp($a['native'], $b['native']);
});
@endphp
<div class="flex ml-auto items-center">
<x-jetstream.dropdown>
<x-slot name="trigger">
<span class="inline-flex rounded-md">
<button class="inline-flex items-center py-2 text-sm font-medium leading-4 text-theme-primary hover:text-theme-secondary focus:bg-theme-surface focus:outline-none active:bg-theme-surface"
type="button">
<x-icon name="globe-alt" class="w-6 h-5" outline />
</button>
</span>
</x-slot>
<x-slot name="content">
<div class="w-60">
@foreach ($supportedLocales as $localeCode => $properties)
@php
$language = $languages->firstWhere('lang_code', strtolower($localeCode));
@endphp
@if ($language)
<div class="block px-4 py-2">
<a class="text-theme-primary transition hover:text-theme-secondary focus:border-theme-primary focus:text-theme-secondary"
href="{{ LaravelLocalization::getLocalizedURL($localeCode, null, [], true) }}"
hreflang="{{ $localeCode }}" rel="alternate">
{{ $language->flag }}
<span class="ml-3 text-theme-primary hove:text-theme-secondary">{{ Lang::get($language->name, [], $localeCode) }}</span>
</a>
</div>
@endif
@endforeach
</div>
</x-slot>
</x-jetstream.dropdown>
</div>
@if (Route::has('login'))
<div class="flex items-center space-x-2 ml-4">
@if (Route::has('register'))
<a href="{{ route('register') }}"
class="text-theme-primary hover:text-theme-secondary py-2 text-sm font-medium focus:outline-none">
{{ __('Register') }}
</a>
@endif
<a href="{{ route('login') }}"
class="text-theme-primary hover:text-theme-secondary pl-3 py-2 text-sm font-medium focus:outline-none">
{{ __('Log in') }}
</a>
</div>
@endif
</div>
</div>
@endif
</nav>

View File

@@ -0,0 +1,15 @@
<x-jetstream.form-section submit="saveTags">
<x-slot name="title">
{{ str_replace('@PLATFORM_NAME@', platform_name(), __('Activies you share on @PLATFORM_NAME@')) }}
</x-slot>
<x-slot name="description">
<p>{{ __('Which activities and skills can you share on Timebank? Give practical examples, avoid vague or general keywords.') }}
<p> {{ __('Rare skills can be interesting for others, but very common activities are also very useful to offer!') }} </p>
</x-slot>
<x-slot name="form">
</div>
@livewire('skills-form')
</x-slot>
</x-jetstream.form-section>

View File

@@ -0,0 +1,6 @@
<x-alert padding="none" title="{{ __('Please add new tags responsibly!') }}" warning>
<x-slot name="slot">
{{ __('Your name will be linked to this tag keyword.') }} <br>
{{ __('New activity tags are reviewed, and inappropriate or useless labels can be removed without notice.') }}
</x-slot>
</x-alert>