@stacksjs/defaults 0.72.49 → 0.72.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ide/vscode/package.json +1 -1
- package/lang/de.yml +0 -1
- package/package.json +2 -2
- package/resources/components/Dashboard/Auth/ForgotPasswordDashboard.stx +8 -0
- package/resources/components/Dashboard/Auth/Login.stx +8 -0
- package/resources/components/Dashboard/Auth/LoginDashboard.stx +5 -1
- package/resources/components/Dashboard/Auth/MagicLink.stx +94 -0
- package/resources/components/Dashboard/Auth/Register.stx +7 -0
- package/resources/components/Dashboard/Auth/RegisterDashboard.stx +5 -1
- package/resources/components/Dashboard/Auth/ResetPassword.stx +163 -0
- package/resources/components/Dashboard/Auth/ResetPasswordDashboard.stx +95 -0
- package/resources/layouts/403.stx +15 -148
- package/resources/layouts/404.stx +4 -1
- package/resources/views/auth/magic/[token].stx +24 -0
- package/resources/views/errors/404.stx +36 -0
- package/resources/views/forgot-password.stx +13 -0
- package/resources/views/login.stx +68 -0
- package/resources/views/password/reset/[token].stx +18 -0
- package/resources/views/register.stx +64 -0
- package/views/[...all].stx +0 -7
- package/views/about.md +0 -3
- package/views/auth/login.stx +0 -1
- package/views/auth/magic/[token].stx +0 -88
- package/views/auth/signup.stx +0 -120
- package/views/forum/category.stx +0 -92
- package/views/forum/index.stx +0 -209
- package/views/forum/new-topic.stx +0 -113
- package/views/forum/profile.stx +0 -1
- package/views/forum/topic.stx +0 -143
- package/views/hello-world/[name].stx +0 -47
- package/views/index.stx +0 -21
package/ide/vscode/package.json
CHANGED
package/lang/de.yml
CHANGED
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/defaults",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.72.
|
|
5
|
+
"version": "0.72.50",
|
|
6
6
|
"description": "The complete managed Stacks application scaffold, including runtime defaults, AI guidance, editor metadata, and npm-backed project support files.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"license": "MIT",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@iconify-json/f7": "^1.2.2",
|
|
48
48
|
"@iconify-json/hugeicons": "^1.2.27",
|
|
49
|
-
"@stacksjs/mobile": "^0.72.
|
|
49
|
+
"@stacksjs/mobile": "^0.72.50",
|
|
50
50
|
"@stacksjs/sanitizer": "^0.2.113"
|
|
51
51
|
},
|
|
52
52
|
"scripts": {
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
<script client>
|
|
2
|
+
// Imported rather than taken as a global, the way LoginDashboard and
|
|
3
|
+
// RegisterDashboard already import useAuth. The bare name only resolves on the
|
|
4
|
+
// dashboard server, so mounting this container anywhere else - such as the
|
|
5
|
+
// site's /forgot-password page - reached the submit handler and threw
|
|
6
|
+
// "dashboardApi is not defined", which the catch below then showed to the
|
|
7
|
+
// visitor as the reason their reset link could not be sent.
|
|
8
|
+
import { dashboardApi } from '../../../../functions/dashboard-api'
|
|
9
|
+
|
|
2
10
|
const isLoading = state(false)
|
|
3
11
|
const error = state('')
|
|
4
12
|
const success = state(false)
|
|
@@ -129,6 +129,14 @@ function handleSubmit(): void {
|
|
|
129
129
|
<span :if="!isLoading()">Sign in</span>
|
|
130
130
|
</Button>
|
|
131
131
|
</form>
|
|
132
|
+
|
|
133
|
+
{{--
|
|
134
|
+
Anything that belongs with the credentials, inside the same card:
|
|
135
|
+
the social sign-in buttons the /login page fills this with. It is a
|
|
136
|
+
slot because the provider list has to be computed server-side by
|
|
137
|
+
`configuredSocialProviders()`, and this component is client-only.
|
|
138
|
+
--}}
|
|
139
|
+
<slot name="social" />
|
|
132
140
|
</div>
|
|
133
141
|
|
|
134
142
|
<p :if="showSignUp()" class="mt-6 text-center text-neutral-500 text-sm dark:text-neutral-400">
|
|
@@ -41,5 +41,9 @@ async function handleLogin(credentials) {
|
|
|
41
41
|
:isLoading="isLoading()"
|
|
42
42
|
:error="error()"
|
|
43
43
|
:data-auth-status="isLoading() ? 'loading' : 'ready'"
|
|
44
|
-
|
|
44
|
+
>
|
|
45
|
+
<!-- Forwarded so the page mounting this container can put social
|
|
46
|
+
sign-in inside the card. -->
|
|
47
|
+
<template #social><slot name="social" /></template>
|
|
48
|
+
</Login>
|
|
45
49
|
</template>
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
<script client>
|
|
2
|
+
/**
|
|
3
|
+
* The magic-link interstitial.
|
|
4
|
+
*
|
|
5
|
+
* The email's GET lands on the page that mounts this, and this posts the token
|
|
6
|
+
* to the consume endpoint - a GET must never consume, because mail-security
|
|
7
|
+
* scanners prefetch links and would burn the single-use token before the
|
|
8
|
+
* person ever taps it. When `autoConsume` is off it waits for a tap instead,
|
|
9
|
+
* for environments whose scanners execute page scripts too.
|
|
10
|
+
*
|
|
11
|
+
* `autoConsume` arrives as a string because it is decided server-side, from
|
|
12
|
+
* `config.auth.magicLink.confirmInteraction`, and a client script cannot read
|
|
13
|
+
* config directly.
|
|
14
|
+
*/
|
|
15
|
+
const autoConsume = useReactiveProp<string>('autoConsume', 'true')
|
|
16
|
+
const route = useRoute()
|
|
17
|
+
const status = state('ready') // ready | working | done | failed
|
|
18
|
+
const message = state('')
|
|
19
|
+
|
|
20
|
+
async function consume() {
|
|
21
|
+
if (status() === 'working' || status() === 'done')
|
|
22
|
+
return
|
|
23
|
+
|
|
24
|
+
status.set('working')
|
|
25
|
+
try {
|
|
26
|
+
const reply = await fetch('/auth/magic-link/consume', {
|
|
27
|
+
method: 'POST',
|
|
28
|
+
headers: { 'Content-Type': 'application/json' },
|
|
29
|
+
credentials: 'same-origin',
|
|
30
|
+
body: JSON.stringify({ token: String(route.params.token || '') }),
|
|
31
|
+
})
|
|
32
|
+
const body = await reply.json().catch(() => ({}))
|
|
33
|
+
|
|
34
|
+
if (!reply.ok) {
|
|
35
|
+
status.set('failed')
|
|
36
|
+
message.set(body.message || body.error || 'That sign-in link is not valid.')
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
status.set('done')
|
|
41
|
+
const redirect = String(body.redirect_to || '/')
|
|
42
|
+
navigate(redirect.startsWith('/') && !redirect.startsWith('//') ? redirect : '/')
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
status.set('failed')
|
|
46
|
+
message.set('Something went wrong. Request a new link and try again.')
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// `consume` reads `status()`, so calling it from inside the effect makes the
|
|
51
|
+
// effect depend on `status` - and every set inside `consume` schedules another
|
|
52
|
+
// run. Its own early return covers 'working' and 'done', but not 'failed', so a
|
|
53
|
+
// link that fails to consume would be retried forever. A plain flag, which is
|
|
54
|
+
// not a signal and so not a dependency, is what makes this fire once.
|
|
55
|
+
let autoStarted = false
|
|
56
|
+
|
|
57
|
+
effect(() => {
|
|
58
|
+
if (autoStarted || autoConsume() !== 'true')
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
autoStarted = true
|
|
62
|
+
consume()
|
|
63
|
+
})
|
|
64
|
+
</script>
|
|
65
|
+
|
|
66
|
+
<template>
|
|
67
|
+
<main class="flex items-center justify-center px-6 min-h-[100dvh]">
|
|
68
|
+
<div class="max-w-sm w-full text-center">
|
|
69
|
+
<div :if="status() === 'failed'">
|
|
70
|
+
<div class="flex items-center justify-center mx-auto h-12 w-12 bg-red-50 rounded-full">
|
|
71
|
+
<span class="h-6 w-6 text-red-500 i-hugeicons-alert-02"></span>
|
|
72
|
+
</div>
|
|
73
|
+
<h1 class="mt-5 font-semibold text-xl tracking-tight">Sign-in link problem</h1>
|
|
74
|
+
<p class="mt-2 text-[15px] text-gray-600">{{ message() }}</p>
|
|
75
|
+
<a href="/login" class="inline-block mt-6 px-5 py-2.5 font-medium text-white bg-gray-900 rounded-lg">Back to sign in</a>
|
|
76
|
+
</div>
|
|
77
|
+
<div :else-if="status() === 'working' || status() === 'done'">
|
|
78
|
+
<div class="flex items-center justify-center mx-auto h-12 w-12 bg-gray-100 rounded-full">
|
|
79
|
+
<span class="h-6 w-6 text-gray-500 animate-spin i-hugeicons-loading-03"></span>
|
|
80
|
+
</div>
|
|
81
|
+
<h1 class="mt-5 font-semibold text-xl tracking-tight">Signing you in</h1>
|
|
82
|
+
<p class="mt-2 text-[15px] text-gray-600">One moment.</p>
|
|
83
|
+
</div>
|
|
84
|
+
<div :else>
|
|
85
|
+
<div class="flex items-center justify-center mx-auto h-12 w-12 bg-gray-100 rounded-full">
|
|
86
|
+
<span class="h-6 w-6 text-gray-600 i-hugeicons-mail-open-01"></span>
|
|
87
|
+
</div>
|
|
88
|
+
<h1 class="mt-5 font-semibold text-xl tracking-tight">Continue to sign in</h1>
|
|
89
|
+
<p class="mt-2 text-[15px] text-gray-600">Tap below to finish signing in with your emailed link.</p>
|
|
90
|
+
<button type="button" @click="consume" class="mt-6 px-6 py-3 font-medium text-white bg-gray-900 rounded-lg transition-transform active:scale-[0.98]">Continue</button>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
</main>
|
|
94
|
+
</template>
|
|
@@ -225,6 +225,13 @@ function handleSubmit(): void {
|
|
|
225
225
|
</Button>
|
|
226
226
|
</form>
|
|
227
227
|
|
|
228
|
+
{{--
|
|
229
|
+
Anything that belongs with the credentials, inside the same card:
|
|
230
|
+
the social sign-in buttons the /register page fills this with. It is
|
|
231
|
+
a slot because the provider list has to be computed server-side by
|
|
232
|
+
`configuredSocialProviders()`, and this component is client-only.
|
|
233
|
+
--}}
|
|
234
|
+
<slot name="social" />
|
|
228
235
|
</div>
|
|
229
236
|
|
|
230
237
|
<!-- Login Link -->
|
|
@@ -41,5 +41,9 @@ async function handleRegister(credentials) {
|
|
|
41
41
|
:isLoading="isLoading()"
|
|
42
42
|
:error="error()"
|
|
43
43
|
:data-auth-status="isLoading() ? 'loading' : 'ready'"
|
|
44
|
-
|
|
44
|
+
>
|
|
45
|
+
<!-- Forwarded so the page mounting this container can put social
|
|
46
|
+
sign-in inside the card. -->
|
|
47
|
+
<template #social><slot name="social" /></template>
|
|
48
|
+
</Register>
|
|
45
49
|
</template>
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
@import('../UI/Input', '../UI/Button')
|
|
2
|
+
|
|
3
|
+
{{--
|
|
4
|
+
The page a mailed reset link lands on. Presentational only: it renders the
|
|
5
|
+
four states the flow has and emits `submit`; ResetPasswordDashboard decides
|
|
6
|
+
which state applies and talks to the API.
|
|
7
|
+
|
|
8
|
+
States, in the order a visitor meets them:
|
|
9
|
+
checking - the token is being verified, before any field is shown
|
|
10
|
+
invalid - the token is expired, already used, or was never real
|
|
11
|
+
success - the password is changed
|
|
12
|
+
(default) - the form
|
|
13
|
+
--}}
|
|
14
|
+
|
|
15
|
+
<script client>
|
|
16
|
+
const emit = defineEmits()
|
|
17
|
+
const title = useReactiveProp<string>('title', 'Choose a new password')
|
|
18
|
+
const subtitle = useReactiveProp<string>('subtitle', 'Set the password you will use to sign in')
|
|
19
|
+
const isLoading = useReactiveProp<boolean>('isLoading', false)
|
|
20
|
+
const error = useReactiveProp<string>('error', '')
|
|
21
|
+
const success = useReactiveProp<boolean>('success', false)
|
|
22
|
+
const checking = useReactiveProp<boolean>('checking', false)
|
|
23
|
+
const invalid = useReactiveProp<boolean>('invalid', false)
|
|
24
|
+
const password = state('')
|
|
25
|
+
const confirmation = state('')
|
|
26
|
+
|
|
27
|
+
function mismatch(): boolean {
|
|
28
|
+
return confirmation().length > 0 && password() !== confirmation()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function canSubmit(): boolean {
|
|
32
|
+
return !isLoading() && password().length > 0 && confirmation().length > 0 && !mismatch()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function handleSubmit(): void {
|
|
36
|
+
if (!canSubmit())
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
emit('submit', {
|
|
40
|
+
password: password(),
|
|
41
|
+
password_confirmation: confirmation(),
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<div class="flex items-center justify-center px-4 py-12 min-h-screen">
|
|
47
|
+
<div class="max-w-sm w-full">
|
|
48
|
+
<!-- Logo/Brand -->
|
|
49
|
+
<div class="mb-8 text-center">
|
|
50
|
+
<div class="inline-flex items-center justify-center mb-4 h-12 w-12 bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl shadow-blue-500/25 shadow-lg">
|
|
51
|
+
<span class="h-7 w-7 text-white i-hugeicons-cube"></span>
|
|
52
|
+
</div>
|
|
53
|
+
<h1 class="font-semibold text-2xl text-neutral-900 dark:text-white">
|
|
54
|
+
{{ title() }}
|
|
55
|
+
</h1>
|
|
56
|
+
<p class="mt-2 text-neutral-500 text-sm dark:text-neutral-400">
|
|
57
|
+
{{ subtitle() }}
|
|
58
|
+
</p>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<!-- Form Card -->
|
|
62
|
+
<div class="p-6 bg-white dark:bg-neutral-900 border border-neutral-200/60 rounded-2xl dark:border-neutral-800 shadow-black/5 shadow-xl dark:shadow-black/20">
|
|
63
|
+
<div :if="checking()">
|
|
64
|
+
<div class="py-4 text-center">
|
|
65
|
+
<div class="inline-flex items-center justify-center mb-4 h-12 w-12 bg-neutral-100 dark:bg-neutral-800 rounded-full">
|
|
66
|
+
<div class="h-6 w-6 text-neutral-500 animate-spin i-hugeicons-loading-03"></div>
|
|
67
|
+
</div>
|
|
68
|
+
<p class="text-neutral-500 text-sm dark:text-neutral-400">Checking your link</p>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<div :else-if="invalid()">
|
|
73
|
+
<div class="py-4 text-center">
|
|
74
|
+
<div class="inline-flex items-center justify-center mb-4 h-12 w-12 bg-red-100 dark:bg-red-900/30 rounded-full">
|
|
75
|
+
<div class="h-6 w-6 text-red-600 dark:text-red-400 i-hugeicons-alert-circle"></div>
|
|
76
|
+
</div>
|
|
77
|
+
<h2 class="mb-2 font-medium text-lg text-neutral-900 dark:text-white">
|
|
78
|
+
This link no longer works
|
|
79
|
+
</h2>
|
|
80
|
+
<p class="mb-6 text-neutral-500 text-sm dark:text-neutral-400">
|
|
81
|
+
Reset links expire, and each one can be used only once. Ask for a fresh link and it will arrive in a moment.
|
|
82
|
+
</p>
|
|
83
|
+
<Button href="/forgot-password">
|
|
84
|
+
Send a new link
|
|
85
|
+
</Button>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<div :else-if="success()">
|
|
90
|
+
<div class="py-4 text-center">
|
|
91
|
+
<div class="inline-flex items-center justify-center mb-4 h-12 w-12 bg-green-100 dark:bg-green-900/30 rounded-full">
|
|
92
|
+
<div class="h-6 w-6 text-green-600 dark:text-green-400 i-hugeicons-checkmark-circle-02"></div>
|
|
93
|
+
</div>
|
|
94
|
+
<h2 class="mb-2 font-medium text-lg text-neutral-900 dark:text-white">
|
|
95
|
+
Password changed
|
|
96
|
+
</h2>
|
|
97
|
+
<p class="mb-6 text-neutral-500 text-sm dark:text-neutral-400">
|
|
98
|
+
You can sign in with your new password now.
|
|
99
|
+
</p>
|
|
100
|
+
<Button href="/login">
|
|
101
|
+
Go to sign in
|
|
102
|
+
</Button>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<div :else>
|
|
107
|
+
<div :if="error()" role="alert" class="mb-4 p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 rounded-lg dark:border-red-800">
|
|
108
|
+
<div class="flex gap-2 items-center">
|
|
109
|
+
<div class="h-4 w-4 text-red-600 dark:text-red-400 i-hugeicons-alert-circle"></div>
|
|
110
|
+
<p class="text-red-600 text-sm dark:text-red-400">{{ error() }}</p>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<form @submit.prevent="handleSubmit">
|
|
115
|
+
<div class="mb-4">
|
|
116
|
+
<Input
|
|
117
|
+
type="password"
|
|
118
|
+
label="New password"
|
|
119
|
+
placeholder="At least 8 characters"
|
|
120
|
+
autocomplete="new-password"
|
|
121
|
+
v-model:value="password"
|
|
122
|
+
required
|
|
123
|
+
:disabled="isLoading()"
|
|
124
|
+
/>
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
<div class="mb-6">
|
|
128
|
+
<Input
|
|
129
|
+
type="password"
|
|
130
|
+
label="Confirm new password"
|
|
131
|
+
placeholder="Repeat your new password"
|
|
132
|
+
autocomplete="new-password"
|
|
133
|
+
v-model:value="confirmation"
|
|
134
|
+
:error="mismatch() ? 'Both passwords must match' : ''"
|
|
135
|
+
required
|
|
136
|
+
:disabled="isLoading()"
|
|
137
|
+
/>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
<Button
|
|
141
|
+
type="submit"
|
|
142
|
+
variant="primary"
|
|
143
|
+
size="lg"
|
|
144
|
+
fullWidth
|
|
145
|
+
:loading="isLoading()"
|
|
146
|
+
:disabled="!canSubmit()"
|
|
147
|
+
>
|
|
148
|
+
<span :if="isLoading()">Saving...</span>
|
|
149
|
+
<span :if="!isLoading()">Change password</span>
|
|
150
|
+
</Button>
|
|
151
|
+
</form>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
<!-- Back to Login -->
|
|
156
|
+
<p class="mt-6 text-center text-neutral-500 text-sm dark:text-neutral-400">
|
|
157
|
+
<a href="/login" class="inline-flex gap-1 items-center font-medium text-blue-600 dark:text-blue-400 hover:text-blue-500 dark:hover:text-blue-300">
|
|
158
|
+
<div class="h-4 w-4 i-hugeicons-arrow-left-01"></div>
|
|
159
|
+
Back to sign in
|
|
160
|
+
</a>
|
|
161
|
+
</p>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
<script client>
|
|
2
|
+
/**
|
|
3
|
+
* Container for the mailed reset link.
|
|
4
|
+
*
|
|
5
|
+
* The link the framework sends is `/password/reset/{token}?email={email}`
|
|
6
|
+
* (core/auth/src/password/reset.ts, overridable via
|
|
7
|
+
* `config.auth.passwordReset.url`), so both halves of the credential come off
|
|
8
|
+
* the URL: the token from the route, the address from the query string.
|
|
9
|
+
*
|
|
10
|
+
* The token is verified before any field is shown. `/password/verify-token`
|
|
11
|
+
* exists for exactly this, and it means an expired link says so immediately
|
|
12
|
+
* instead of after someone has chosen and typed a new password twice.
|
|
13
|
+
*/
|
|
14
|
+
import { dashboardApi } from '../../../../functions/dashboard-api'
|
|
15
|
+
|
|
16
|
+
const route = useRoute()
|
|
17
|
+
|
|
18
|
+
const checking = state(true)
|
|
19
|
+
const invalid = state(false)
|
|
20
|
+
const isLoading = state(false)
|
|
21
|
+
const error = state('')
|
|
22
|
+
const success = state(false)
|
|
23
|
+
|
|
24
|
+
function token(): string {
|
|
25
|
+
return String(route.params.token || '')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function email(): string {
|
|
29
|
+
return String(route.query.email || '')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function verify(): Promise<void> {
|
|
33
|
+
if (!token() || !email()) {
|
|
34
|
+
checking.set(false)
|
|
35
|
+
invalid.set(true)
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
await dashboardApi('/password/verify-token', {
|
|
41
|
+
method: 'POST',
|
|
42
|
+
auth: false,
|
|
43
|
+
body: { token: token(), email: email() },
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Any failure here is the link, not the network: a token that verifies
|
|
48
|
+
// is the only thing that lets the form be worth showing.
|
|
49
|
+
invalid.set(true)
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
checking.set(false)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function handleReset(data): Promise<void> {
|
|
57
|
+
isLoading.set(true)
|
|
58
|
+
error.set('')
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
await dashboardApi('/password/reset', {
|
|
62
|
+
method: 'POST',
|
|
63
|
+
auth: false,
|
|
64
|
+
body: {
|
|
65
|
+
token: token(),
|
|
66
|
+
email: email(),
|
|
67
|
+
password: data.password,
|
|
68
|
+
password_confirmation: data.password_confirmation,
|
|
69
|
+
},
|
|
70
|
+
})
|
|
71
|
+
success.set(true)
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
error.set(err instanceof Error ? err.message : 'Could not change your password')
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
isLoading.set(false)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
effect(() => {
|
|
82
|
+
verify()
|
|
83
|
+
})
|
|
84
|
+
</script>
|
|
85
|
+
|
|
86
|
+
<template>
|
|
87
|
+
<ResetPassword
|
|
88
|
+
@submit="handleReset"
|
|
89
|
+
:checking="checking()"
|
|
90
|
+
:invalid="invalid()"
|
|
91
|
+
:isLoading="isLoading()"
|
|
92
|
+
:error="error()"
|
|
93
|
+
:success="success()"
|
|
94
|
+
/>
|
|
95
|
+
</template>
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
<script server>
|
|
2
|
+
// Laravel 5.7's 403 illustration (MIT), the same scene the framework's own
|
|
3
|
+
// error pages use, so an app that opts into this layout matches them. Pass
|
|
4
|
+
// `backgroundImage` to swap it for anything else.
|
|
5
|
+
const backgroundImage = props.backgroundImage || '/svgs/403.svg'
|
|
2
6
|
const appName = props.appName || 'Your App'
|
|
3
7
|
const supportEmail = props.supportEmail || 'support@example.com'
|
|
4
8
|
</script>
|
|
@@ -17,30 +21,20 @@ const supportEmail = props.supportEmail || 'support@example.com'
|
|
|
17
21
|
<h2 class="mb-4 font-bold text-6xl text-slate-900">
|
|
18
22
|
Access Denied
|
|
19
23
|
</h2>
|
|
20
|
-
<p class="mb-8 font-bold text-8xl
|
|
24
|
+
<p class="mb-8 font-bold text-8xl">
|
|
21
25
|
403
|
|
22
26
|
</p>
|
|
23
27
|
<p class="mb-8 text-slate-600 text-xl">
|
|
24
28
|
Sorry, you don't have permission to access this page.
|
|
25
29
|
</p>
|
|
26
30
|
|
|
27
|
-
<
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
>
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
</a>
|
|
35
|
-
|
|
36
|
-
<a
|
|
37
|
-
href="javascript:history.back()"
|
|
38
|
-
class="flex items-center justify-center px-6 py-3 w-full sm:w-auto text-slate-700 hover:bg-slate-50 border border-slate-300 rounded-lg duration-200 transition-colors"
|
|
39
|
-
>
|
|
40
|
-
<i class="mr-2 h-5 w-5 i-hugeicons-arrow-left-02"></i>
|
|
41
|
-
Go Back
|
|
42
|
-
</a>
|
|
43
|
-
</div>
|
|
31
|
+
<a
|
|
32
|
+
href="/"
|
|
33
|
+
class="inline-flex items-center justify-center px-6 py-3 text-white bg-slate-500 hover:bg-slate-600 rounded-lg duration-200 transition-colors"
|
|
34
|
+
>
|
|
35
|
+
<i class="mr-2 h-5 w-5 i-hugeicons-home-03"></i>
|
|
36
|
+
Back to Home
|
|
37
|
+
</a>
|
|
44
38
|
</div>
|
|
45
39
|
|
|
46
40
|
<div class="mt-16 pt-8 border-slate-200 border-t">
|
|
@@ -54,135 +48,8 @@ const supportEmail = props.supportEmail || 'support@example.com'
|
|
|
54
48
|
</div>
|
|
55
49
|
</div>
|
|
56
50
|
|
|
57
|
-
<!-- Right side
|
|
58
|
-
<div class="
|
|
59
|
-
<
|
|
60
|
-
<defs>
|
|
61
|
-
<linearGradient id="a" x1="50%" x2="50%" y1="100%" y2="0%">
|
|
62
|
-
<stop offset="0" stop-color="#76c3c3"/>
|
|
63
|
-
<stop offset="1" stop-color="#183468"/>
|
|
64
|
-
</linearGradient>
|
|
65
|
-
<linearGradient id="b" x1="100%" x2="0%" y1="50%" y2="50%">
|
|
66
|
-
<stop offset="0" stop-color="#486587"/>
|
|
67
|
-
<stop offset=".3323" stop-color="#183352"/>
|
|
68
|
-
<stop offset=".6667" stop-color="#264a6e"/>
|
|
69
|
-
<stop offset="1" stop-color="#183352"/>
|
|
70
|
-
</linearGradient>
|
|
71
|
-
<linearGradient id="c" x1="49.87%" x2="48.5%" y1="3.62%" y2="100%">
|
|
72
|
-
<stop offset="0" stop-color="#e0f2fa"/>
|
|
73
|
-
<stop offset=".0898" stop-color="#89bed6"/>
|
|
74
|
-
<stop offset=".3298" stop-color="#1e3c6e"/>
|
|
75
|
-
<stop offset="1" stop-color="#1b376b"/>
|
|
76
|
-
</linearGradient>
|
|
77
|
-
<linearGradient id="d">
|
|
78
|
-
<stop offset="0" stop-color="#b0ddf1"/>
|
|
79
|
-
<stop offset="1" stop-color="#325c82"/>
|
|
80
|
-
</linearGradient>
|
|
81
|
-
<linearGradient id="e" x1="49.87%" x2="49.87%" xlink:href="#d" y1="3.62%" y2="77.75%"/>
|
|
82
|
-
<linearGradient id="f">
|
|
83
|
-
<stop offset="0" stop-color="#1d3a6d"/>
|
|
84
|
-
<stop offset="1" stop-color="#467994"/>
|
|
85
|
-
</linearGradient>
|
|
86
|
-
<linearGradient id="g" x1="91.59%" x2="66.97%" xlink:href="#f" y1="5.89%" y2="100%"/>
|
|
87
|
-
<linearGradient id="h" x1="97.27%" x2="52.53%" xlink:href="#f" y1="6.88%" y2="100%"/>
|
|
88
|
-
<linearGradient id="i" x1="82.73%" x2="41.46%" xlink:href="#f" y1="41.06%" y2="167.23%"/>
|
|
89
|
-
<linearGradient id="j" x1="49.87%" x2="49.87%" xlink:href="#d" y1="3.62%" y2="100.77%"/>
|
|
90
|
-
<linearGradient id="k" x1="100%" x2="72.45%" xlink:href="#f" y1="0%" y2="85.2%"/>
|
|
91
|
-
<linearGradient id="l" x1="100%" x2="62.1%" y1="0%" y2="68.86%">
|
|
92
|
-
<stop offset="0" stop-color="#163055"/>
|
|
93
|
-
<stop offset="1" stop-color="#2f587f"/>
|
|
94
|
-
</linearGradient>
|
|
95
|
-
<circle id="m" cx="180" cy="102" r="40"/>
|
|
96
|
-
<filter id="n" height="340%" width="340%" x="-120%" y="-120%">
|
|
97
|
-
<feOffset in="SourceAlpha" result="shadowOffsetOuter1"/>
|
|
98
|
-
<feGaussianBlur in="shadowOffsetOuter1" result="shadowBlurOuter1" stdDeviation="32"/>
|
|
99
|
-
<feColorMatrix in="shadowBlurOuter1" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.696473053 0"/>
|
|
100
|
-
</filter>
|
|
101
|
-
<linearGradient id="o" x1="0%" y1="50%" y2="50%">
|
|
102
|
-
<stop offset="0" stop-color="#fff" stop-opacity="0"/>
|
|
103
|
-
<stop offset="1" stop-color="#fff"/>
|
|
104
|
-
</linearGradient>
|
|
105
|
-
</defs>
|
|
106
|
-
<g fill="none" fill-rule="evenodd">
|
|
107
|
-
<path d="m0 0h1024v1024h-1024z" fill="url(#a)"/>
|
|
108
|
-
<g transform="translate(761 481)">
|
|
109
|
-
<path d="m96 27 4-1v11h-4z" fill="#8dbcd2"/>
|
|
110
|
-
<path d="m76 23 4-1v15h-4z" fill="#8dbcd2"/>
|
|
111
|
-
<g fill="#183352">
|
|
112
|
-
<path d="m40 22 4 1v14h-4z"/>
|
|
113
|
-
<path d="m20 26 4 1v14h-4z"/>
|
|
114
|
-
<path d="m59 0h2v20h-2z" opacity=".5"/>
|
|
115
|
-
</g>
|
|
116
|
-
<path d="m61 0c3 0 3 2 6 2s3-2 6-2 3 2 6 2v8c-3 0-3-2-6-2s-3 2-6 2-3-2-6-2z" fill="url(#b)"/>
|
|
117
|
-
<path d="m50 20 10-2v110h-60l10-100 10-2v10.92l10-.98v-11.94l10-2v12.96l10-.98z" fill="#8dbcd2"/>
|
|
118
|
-
<path d="m100 26 10 2 10 100h-60v-110l10 2v13.98l10 .98v-12.96l10 2v11.94l10 .98z" fill="#183352"/>
|
|
119
|
-
</g>
|
|
120
|
-
<g transform="translate(0 565)">
|
|
121
|
-
<path d="m1024 385h-1024v-278.14c118.4 21.09 185.14 57.03 327.4 48.14 198.54-12.4 250-125 500-125 90.18 0 147.92 16.3 196.6 37.12z" fill="url(#c)"/>
|
|
122
|
-
<path d="m1024 355h-1024v-275.44c76.46-35.75 137.14-79.56 285-79.56 250 0 301.46 112.6 500 125 103.24 6.45 166.7-10.7 239-28.66z" fill="url(#e)"/>
|
|
123
|
-
<path d="m344.12 130.57c23.1 13.47-25.27 81.95-145.12 205.43h649c-344.06-141.7-512.02-210.17-503.88-205.43z" fill="url(#e)"/>
|
|
124
|
-
<path d="m0 336v-256.44c76.46-35.75 137.14-79.56 285-79.56 71.14 0 86.22 26.04 32.5 82-48 50 147.33 58.02 36 136.5-40.67 28.67 21.17 67.83 185.5 117.5z" fill="url(#g)"/>
|
|
125
|
-
<path d="m317.5 82c-48 50 147.33 58.02 36 136.5-40.67 28.67 21.17 67.83 185.5 117.5h-484z" fill="url(#h)"/>
|
|
126
|
-
<path d="m353.5 218.5c-40.67 28.67 21.17 67.83 185.5 117.5h-364z" fill="url(#i)"/>
|
|
127
|
-
<path d="m0 459v-194.46c100.25 21.2 167.18 50.29 296.67 42.19 198.57-12.43 250.04-125.15 500.07-125.15 109.75 0 171.47 24.16 227.26 51.25v226.17z" fill="url(#j)"/>
|
|
128
|
-
<path d="m1024 459h-177.84c51.95-58.9 48.86-97.16-9.28-114.78-186.64-56.58-101.76-162.64-39.97-162.64 109.64 0 171.34 24.12 227.09 51.19z" fill="url(#k)"/>
|
|
129
|
-
<path d="m1024 459h-177.81c52.01-59.01 48.94-97.34-9.22-115l187.03 53.48z" fill="url(#l)"/>
|
|
130
|
-
</g>
|
|
131
|
-
<g id="moonGroup">
|
|
132
|
-
<g transform="translate(94 23)">
|
|
133
|
-
<use fill="#000" filter="url(#n)" xlink:href="#m"/>
|
|
134
|
-
<use fill="#d2f1fe" xlink:href="#m"/>
|
|
135
|
-
</g>
|
|
136
|
-
</g>
|
|
137
|
-
<g transform="translate(94 23)">
|
|
138
|
-
<use fill="#000" filter="url(#n)" xlink:href="#m"/>
|
|
139
|
-
<g fill="#fff">
|
|
140
|
-
<circle cx="123" cy="255" fill-opacity=".4" r="3"/>
|
|
141
|
-
<circle cx="2" cy="234" r="2"/>
|
|
142
|
-
<circle cx="33" cy="65" r="3"/>
|
|
143
|
-
<circle cx="122" cy="2" r="2"/>
|
|
144
|
-
<circle cx="72" cy="144" r="2"/>
|
|
145
|
-
<circle cx="282" cy="224" r="2"/>
|
|
146
|
-
<circle cx="373" cy="65" opacity=".4" r="3"/>
|
|
147
|
-
<circle cx="433" cy="255" r="3"/>
|
|
148
|
-
</g>
|
|
149
|
-
<path d="m373.25 325.25a5 5 0 0 0 0-10h-75v10z" fill="url(#o)" opacity=".4" transform="matrix(.70710678 .70710678 -.70710678 .70710678 325.523078 -145.38023)"/>
|
|
150
|
-
<g fill="#fff">
|
|
151
|
-
<circle cx="363" cy="345" r="3"/>
|
|
152
|
-
<circle cx="513" cy="115" r="3"/>
|
|
153
|
-
<circle cx="723" cy="5" opacity=".4" r="3"/>
|
|
154
|
-
<circle cx="422" cy="134" r="2"/>
|
|
155
|
-
<circle cx="752" cy="204" r="2"/>
|
|
156
|
-
<circle cx="672" cy="114" r="2"/>
|
|
157
|
-
<circle cx="853" cy="255" opacity=".4" r="3"/>
|
|
158
|
-
<circle cx="623" cy="225" r="3"/>
|
|
159
|
-
<circle cx="823" cy="55" r="3"/>
|
|
160
|
-
<circle cx="902" cy="144" r="2"/>
|
|
161
|
-
<circle cx="552" cy="14" r="2"/>
|
|
162
|
-
</g>
|
|
163
|
-
</g>
|
|
164
|
-
<path d="m796 535a4 4 0 0 1 4 4v20h-8v-20a4 4 0 0 1 4-4z" fill="#486587"/>
|
|
165
|
-
<path d="m798 535.54a4 4 0 0 0 -2 3.46v20h-4v-20a4 4 0 0 1 6-3.46zm48-.54a4 4 0 0 1 4 4v20h-8v-20a4 4 0 0 1 4-4z" fill="#071423"/>
|
|
166
|
-
<path d="m846 559v-20a4 4 0 0 0 -2-3.46 4 4 0 0 1 6 3.46v20z" fill="#8dbcd2"/>
|
|
167
|
-
<path d="m554.67 131.48a9.46 9.46 0 0 1 13.33 0 9.46 9.46 0 0 0 13.33 0l13.33-13.24a28.39 28.39 0 0 1 40 0l10 9.93a14.2 14.2 0 0 0 20 0 14.2 14.2 0 0 1 20 0l.6.6a31.8 31.8 0 0 1 9.4 22.56h-146.66v-3.84c0-6.01 2.4-11.78 6.67-16.01zm196.33-123.23c11.07-11 28.93-11 40 0l10 9.94a14.19 14.19 0 0 0 20 0 14.19 14.19 0 0 1 20 0 16.36 16.36 0 0 0 21.3 1.5l8.7-6.47a33.47 33.47 0 0 1 40 0l4.06 3.03a39.6 39.6 0 0 1 15.94 31.75h-200c0-12.72 8.93-28.75 20-39.75zm-736.9 66.89.9-.9a21.29 21.29 0 0 1 30 0 21.29 21.29 0 0 0 30 0l10-9.93a35.48 35.48 0 0 1 50 0l15 14.9a14.2 14.2 0 0 0 20 0 14.2 14.2 0 0 1 20 0c6.4 6.35 10 15 10 24.02v5.77h-200c0-12.71 5.07-24.9 14.1-33.86z" fill="#fff" opacity=".07" transform="translate(54 301)"/>
|
|
168
|
-
</g>
|
|
169
|
-
</svg>
|
|
51
|
+
<!-- Right side illustration -->
|
|
52
|
+
<div class="overflow-hidden relative h-64 w-full sm:h-80 lg:h-auto lg:w-1/2">
|
|
53
|
+
<img class="absolute inset-0 object-cover h-full w-full" src="{{ backgroundImage }}" alt="">
|
|
170
54
|
</div>
|
|
171
55
|
</div>
|
|
172
|
-
|
|
173
|
-
<style>
|
|
174
|
-
@keyframes floatMoon {
|
|
175
|
-
0% { transform: translateX(0); }
|
|
176
|
-
50% { transform: translateX(20px); }
|
|
177
|
-
100% { transform: translateX(0); }
|
|
178
|
-
}
|
|
179
|
-
#moonGroup {
|
|
180
|
-
animation: floatMoon 4s ease-in-out infinite;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
@keyframes waveTop {
|
|
184
|
-
0% { transform: translateY(0); }
|
|
185
|
-
50% { transform: translateY(-2px); }
|
|
186
|
-
100% { transform: translateY(0); }
|
|
187
|
-
}
|
|
188
|
-
</style>
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
<script server>
|
|
2
|
-
|
|
2
|
+
// Laravel 5.7's 404 illustration (MIT), the same scene the framework's own
|
|
3
|
+
// error pages use, so an app that opts into this layout matches them. Pass
|
|
4
|
+
// `backgroundImage` to swap it for anything else.
|
|
5
|
+
const backgroundImage = props.backgroundImage || '/svgs/404.svg'
|
|
3
6
|
</script>
|
|
4
7
|
|
|
5
8
|
<div class="flex relative justify-center shrink-0 md:px-12 lg:px-0 min-h-full">
|