@edgedev/create-edge-site 1.0.0

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/.nvmrc ADDED
@@ -0,0 +1 @@
1
+ 22.13.1
@@ -0,0 +1,21 @@
1
+ {
2
+ "files.associations": {
3
+ "*.json": "json"
4
+ },
5
+ "[json]": {
6
+ "editor.formatOnSave": true
7
+ },
8
+ "editor.formatOnSave": false,
9
+ "vetur.validation.template": false,
10
+ "editor.codeActionsOnSave": {
11
+ "source.fixAll.eslint": "explicit"
12
+ },
13
+ "editor.formatOnSaveMode": "modifications",
14
+ "editor.trimFinalNewlines": false,
15
+ "vue.features.codeActions": false,
16
+ "editor.tabSize": 2,
17
+ "editor.insertSpaces": true,
18
+ "editor.detectIndentation": false,
19
+ "vue3snippets.enable-compile-vue-file-on-did-save-code": false
20
+ }
21
+
package/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # Edge Website Starter
2
+ ## Setup
3
+
4
+ ```bash
5
+ #Run this under current version of node once:
6
+ npm install -g --ignore-scripts @edgedev/create-edge-site
7
+
8
+ #Then run this whenever you want to create a new app:
9
+ npx @edgedev/create-edge-site yourappname
package/app.vue ADDED
@@ -0,0 +1,5 @@
1
+ <template class="font-sans">
2
+ <edge-navbar />
3
+ <NuxtPage />
4
+ <edge-footer class="h-[200px]" />
5
+ </template>
@@ -0,0 +1,3 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
package/bin/cli.js ADDED
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env node
2
+ console.log('Script started')
3
+ const { execSync } = require('node:child_process')
4
+ const fs = require('node:fs')
5
+ const path = require('node:path')
6
+
7
+ const runCommand = (command) => {
8
+ try {
9
+ console.log(`Running command: ${command}`)
10
+ execSync(`${command}`, { stdio: 'inherit' })
11
+ }
12
+ catch (err) {
13
+ console.error(`Failed to run command: ${command}`, err)
14
+ return false
15
+ }
16
+ return true
17
+ }
18
+
19
+ const modifyPackageJson = (repoName) => {
20
+ try {
21
+ const packageJsonPath = path.join(repoName, 'package.json')
22
+ const packageJsonData = fs.readFileSync(packageJsonPath, 'utf-8')
23
+ const packageJsonObj = JSON.parse(packageJsonData)
24
+
25
+ // Modify the properties
26
+ delete packageJsonObj.version
27
+ delete packageJsonObj.bin
28
+ packageJsonObj.name = repoName
29
+ packageJsonObj.description = `A really cool Edge Site for ${repoName}`
30
+
31
+ // Write the file back
32
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJsonObj, null, 2))
33
+ }
34
+ catch (err) {
35
+ console.error('Failed to modify package.json', err)
36
+ return false
37
+ }
38
+ return true
39
+ }
40
+
41
+ const process = require('node:process')
42
+
43
+ const repoName = process.argv[2]
44
+
45
+ const gitCheckoutCommand = `git clone --depth 1 https://github.com/Edge-Marketing-and-Design/edge-site-starter.git ${repoName}`
46
+ const removeGitDirCommand = `rm -rf ${repoName}/.git`
47
+ const installDependenciesCommand = `cd ${repoName} && pnpm store prune && pnpm install --force --ignore-scripts=false`
48
+
49
+ console.log(`Cloning with name ${repoName}...`)
50
+ const checkedOut = runCommand(gitCheckoutCommand)
51
+ if (!checkedOut) {
52
+ process.exit(1)
53
+ }
54
+
55
+ console.log(`Removing .git directory from ${repoName}...`)
56
+ const removedGitDir = runCommand(removeGitDirCommand)
57
+ if (!removedGitDir) {
58
+ process.exit(1)
59
+ }
60
+
61
+ console.log(`Modifying package.json for ${repoName}...`)
62
+ const modifiedPackageJson = modifyPackageJson(repoName)
63
+ if (!modifiedPackageJson) {
64
+ process.exit(1)
65
+ }
66
+
67
+ console.log(`Installing dependencies for ${repoName}...`)
68
+ const installedDeps = runCommand(installDependenciesCommand)
69
+ if (!installedDeps) {
70
+ process.exit(1)
71
+ }
72
+
73
+ console.log(`Successfully created ${repoName}!`)
74
+ console.log(`cd into ${repoName} and run 'sh firebase_init.sh' to initialize your firebase project.`)
@@ -0,0 +1,43 @@
1
+ <script>
2
+ import { MaskInput } from 'vue-3-mask'
3
+
4
+ export default {
5
+ inheritAttrs: false,
6
+ }
7
+ </script>
8
+
9
+ <script setup>
10
+ const props = defineProps({
11
+ errorClass: {
12
+ type: String,
13
+ required: false,
14
+ default: '',
15
+ },
16
+ name: {
17
+ type: String,
18
+ required: true,
19
+ },
20
+ type: {
21
+ type: String,
22
+ default: 'text',
23
+ },
24
+ })
25
+ </script>
26
+
27
+ defineOptions({ inheritAttrs: false })
28
+
29
+ <template>
30
+ <Field v-slot="{ field }" :name="props.name">
31
+ <MaskInput
32
+ v-if="props.type === 'phone'"
33
+ mask="(###) ###-####"
34
+ type="props.type"
35
+ v-bind="{ ...field, ...$attrs }"
36
+ />
37
+ <input
38
+ v-else
39
+ v-bind="{ ...field, ...$attrs }"
40
+ >
41
+ </Field>
42
+ <ErrorMessage :class="props.errorClass" :name="props.name" />
43
+ </template>
@@ -0,0 +1,34 @@
1
+ <script>
2
+ export default {
3
+ inheritAttrs: false,
4
+ }
5
+ </script>
6
+
7
+ <script setup>
8
+ const props = defineProps({
9
+ errorClass: {
10
+ type: String,
11
+ default: '',
12
+ },
13
+ name: {
14
+ type: String,
15
+ required: true,
16
+ },
17
+ options: {
18
+ type: Array,
19
+ required: true,
20
+ // Example format: [{ label: 'Option 1', value: '1' }]
21
+ },
22
+ })
23
+ </script>
24
+
25
+ <template>
26
+ <Field v-slot="{ field }" :name="props.name">
27
+ <select v-bind="{ ...field, ...$attrs }">
28
+ <option v-for="option in props.options" :key="option.value" :value="option.value">
29
+ {{ option.label }}
30
+ </option>
31
+ </select>
32
+ </Field>
33
+ <ErrorMessage :class="props.errorClass" :name="props.name" />
34
+ </template>
@@ -0,0 +1,29 @@
1
+ <script>
2
+ export default {
3
+ inheritAttrs: false,
4
+ }
5
+ </script>
6
+
7
+ <script setup>
8
+ // or any key you expect
9
+ const props = defineProps({
10
+ errorClass: {
11
+ type: String,
12
+ required: false,
13
+ default: '',
14
+ },
15
+ name: {
16
+ type: String,
17
+ required: true,
18
+ },
19
+ })
20
+ </script>
21
+
22
+ <template>
23
+ <Field v-slot="{ field }" :name="props.name">
24
+ <textarea
25
+ v-bind="{ ...field, ...$attrs }"
26
+ />
27
+ </Field>
28
+ <ErrorMessage :class="props.errorClass" :name="props.name" />
29
+ </template>
@@ -0,0 +1,52 @@
1
+ <template>
2
+ <footer class="bg-black items-center justify-center flex flex-col">
3
+ <div class="text-center">
4
+ <!-- Social Icons -->
5
+ <div class="flex justify-center mb-6 space-x-4">
6
+ <a
7
+ href="#"
8
+ class="flex items-center justify-center w-12 h-12 transition rounded-full bg-lblue text-dblue hover:bg-opacity-80"
9
+ >
10
+ <i class="fab fa-facebook-f fa-lg" />
11
+ </a>
12
+ <a
13
+ href="#"
14
+ class="flex items-center justify-center w-12 h-12 transition rounded-full bg-lblue text-dblue hover:bg-opacity-80"
15
+ >
16
+ <i class="fab fa-instagram fa-lg" />
17
+ </a>
18
+ <a
19
+ href="#"
20
+ class="flex items-center justify-center w-12 h-12 transition rounded-full bg-lblue text-dblue hover:bg-opacity-80"
21
+ >
22
+ <i class="fab fa-houzz fa-lg" /> <!-- Houzz or custom icon -->
23
+ </a>
24
+ </div>
25
+
26
+ <!-- Contact Information -->
27
+ <div
28
+ class="mb-8 text-sm font-medium tracking-wide text-center uppercase text-lblue md:flex md:justify-center md:space-x-8 md:space-y-0"
29
+ >
30
+ <div>
31
+ <a href="tel:+19999999"><span
32
+ class="font-serif font-bold tracking-wider text-white"
33
+ >Office</span> 999.999.9999</a>
34
+ </div>
35
+ <div>
36
+ <span class="font-serif font-bold tracking-wider text-white">Mail</span> Mailing Address
37
+ </div>
38
+ <div>
39
+ <span class="font-serif font-bold tracking-wider text-white">Other Info</span> Other Info
40
+ </div>
41
+ </div>
42
+
43
+ <!-- Logo -->
44
+ <div class="flex justify-center">
45
+ <img src="/images/logo.png" alt="JDL Construction Logo" class="h-auto opacity-75 w-46">
46
+ </div>
47
+ </div>
48
+ </footer>
49
+ </template>
50
+
51
+ <style scoped>
52
+ </style>
@@ -0,0 +1,124 @@
1
+ <script setup>
2
+ import VueTurnstile from 'vue-turnstile'
3
+
4
+ const props = defineProps({
5
+ validationSchema: {
6
+ type: Object,
7
+ required: true,
8
+ },
9
+ formFlingEndpoint: {
10
+ type: String,
11
+ required: true,
12
+ },
13
+ turnstileSiteSecret: {
14
+ type: String,
15
+ required: false,
16
+ },
17
+ successMessage: {
18
+ type: String,
19
+ required: false,
20
+ default: 'Form submitted successfully!',
21
+ },
22
+ errorMessage: {
23
+ type: String,
24
+ required: false,
25
+ default: 'Submission failed, please try again.',
26
+ },
27
+ successClass: {
28
+ type: String,
29
+ required: false,
30
+ default: 'text-green-500 mt-2',
31
+ },
32
+ errorClass: {
33
+ type: String,
34
+ required: false,
35
+ default: 'text-red-500 mt-2',
36
+ },
37
+ })
38
+
39
+ const state = reactive({
40
+ submitting: false,
41
+ turnstileToken: '',
42
+ submitResponse: {
43
+ sent: false,
44
+ success: false,
45
+ message: '',
46
+ },
47
+ })
48
+
49
+ const onSubmit = async (values, { resetForm }) => {
50
+ state.submitting = true
51
+ state.submitResponse.sent = false
52
+
53
+ try {
54
+ const formData = new FormData()
55
+
56
+ for (const key in values) {
57
+ formData.append(key, values[key])
58
+ }
59
+
60
+ if (state.turnstileToken) {
61
+ formData.append('cf-turnstile-response', state.turnstileToken)
62
+ }
63
+
64
+ const response = await fetch(props.formFlingEndpoint, {
65
+ method: 'POST',
66
+ body: formData,
67
+ })
68
+
69
+ state.submitResponse.sent = true
70
+
71
+ if (!response.ok) {
72
+ state.submitResponse.success = false
73
+ state.submitResponse.message = props.errorMessage
74
+ return
75
+ }
76
+
77
+ const data = await response.json()
78
+
79
+ let success = true
80
+ let errorMessage = props.successMessage
81
+
82
+ if (Array.isArray(data)) {
83
+ for (const step of data) {
84
+ if (!step.response.success) {
85
+ success = false
86
+ errorMessage = props.errorMessage
87
+ break
88
+ }
89
+ }
90
+ }
91
+
92
+ state.submitResponse.success = success
93
+ state.submitResponse.message = errorMessage
94
+ }
95
+ catch (err) {
96
+ console.log(err)
97
+ state.submitResponse.success = false
98
+ state.submitResponse.message = props.errorMessage
99
+ }
100
+ finally {
101
+ state.submitting = false
102
+ }
103
+ if (state.submitResponse.success) {
104
+ resetForm()
105
+ }
106
+ }
107
+ </script>
108
+
109
+ <template>
110
+ <Form
111
+ :validation-schema="props.validationSchema"
112
+ @submit="onSubmit"
113
+ >
114
+ <slot :submitting="state.submitting" :submit-response="state.submitResponse" />
115
+ <VueTurnstile
116
+ v-if="props.turnstileSiteSecret"
117
+ v-model="state.turnstileToken"
118
+ :site-key="props.turnstileSiteSecret"
119
+ />
120
+ <div v-if="state.submitResponse.sent" :class="state.submitResponse.success ? props.successClass : props.errorClass">
121
+ {{ state.submitResponse.message }}
122
+ </div>
123
+ </Form>
124
+ </template>
@@ -0,0 +1,167 @@
1
+ <script setup>
2
+ import { onMounted, onUnmounted, ref } from 'vue'
3
+
4
+ const menuOpen = ref(false)
5
+
6
+ const toggleMenu = () => {
7
+ menuOpen.value = !menuOpen.value
8
+ if (menuOpen.value) {
9
+ document.body.classList.add('overflow-hidden') // Prevent scrolling when open
10
+ }
11
+ else {
12
+ document.body.classList.remove('overflow-hidden')
13
+ }
14
+ }
15
+
16
+ // Automatically close the menu when clicking a link
17
+ const closeMenu = () => {
18
+ menuOpen.value = false
19
+ document.body.classList.remove('overflow-hidden')
20
+ }
21
+
22
+ // Ensure the menu closes when resizing back to desktop
23
+ const handleResize = () => {
24
+ if (window.innerWidth >= 1024) {
25
+ closeMenu()
26
+ }
27
+ }
28
+
29
+ // Listen for window resize events
30
+ onMounted(() => {
31
+ window.addEventListener('resize', handleResize)
32
+ })
33
+
34
+ onUnmounted(() => {
35
+ window.removeEventListener('resize', handleResize)
36
+ })
37
+ </script>
38
+
39
+ <template>
40
+ <nav class="relative z-30 text-white">
41
+ <!-- Navigation Content (Top Half) -->
42
+ <div class="relative px-6 md:px-12 bg-dblue">
43
+ <div class="container mx-auto flex items-center justify-between h-[128px]">
44
+ <!-- Logo -->
45
+ <NuxtLink to="/" class="text-xl font-bold">
46
+ <img src="/images/logo.png" alt="JDL Construction MT" class="w-full h-auto">
47
+ </NuxtLink>
48
+
49
+ <!-- Mobile Menu Button -->
50
+ <button class="md:hidden focus:outline-none" @click="toggleMenu">
51
+ <svg v-if="!menuOpen" class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
52
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
53
+ </svg>
54
+ <svg v-else class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
55
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
56
+ </svg>
57
+ </button>
58
+
59
+ <!-- Desktop Menu -->
60
+ <ul class="hidden md:flex space-x-[30px] font-semibold tracking-widest uppercase">
61
+ <li>
62
+ <NuxtLink to="/" class="nav-item">
63
+ Home
64
+ </NuxtLink>
65
+ </li>
66
+ <li>
67
+ <NuxtLink to="/contact" class="nav-item">
68
+ Contact
69
+ </NuxtLink>
70
+ </li>
71
+ </ul>
72
+ </div>
73
+ </div>
74
+
75
+ <!-- Mobile Slide-in Menu -->
76
+ <transition name="slide">
77
+ <div v-if="menuOpen" class="fixed inset-0 flex flex-col items-center justify-center px-20 py-6 text-center bg-dblue z-[9999] w-full h-full">
78
+ <!-- Close Button -->
79
+ <button class="absolute text-4xl top-6 right-6 text-lblue" @click="toggleMenu">
80
+ &times;
81
+ </button>
82
+
83
+ <!-- Mobile Logo -->
84
+ <a href="/"><img src="/images/logo.png" alt="JDL Construction Logo" class="mb-4 w-50"></a>
85
+
86
+ <!-- Social Media Icons -->
87
+ <div class="flex justify-center my-6 space-x-4">
88
+ <a href="#" class="flex items-center justify-center w-12 h-12 transition rounded-full bg-lblue text-dblue hover:bg-opacity-80">
89
+ <i class="fab fa-facebook-f fa-lg" />
90
+ </a>
91
+ <a href="#" class="flex items-center justify-center w-12 h-12 transition rounded-full bg-lblue text-dblue hover:bg-opacity-80">
92
+ <i class="fab fa-instagram fa-lg" />
93
+ </a>
94
+ <a href="#" class="flex items-center justify-center w-12 h-12 transition rounded-full bg-lblue text-dblue hover:bg-opacity-80">
95
+ <i class="fab fa-houzz fa-lg" /> <!-- Houzz or custom icon -->
96
+ </a>
97
+ </div>
98
+
99
+ <!-- Mobile Menu Items -->
100
+ <ul class="w-full">
101
+ <li class="border-t border-lblue">
102
+ <NuxtLink to="/" class="text-lg tracking-widest uppercase" @click="closeMenu">
103
+ Home
104
+ </NuxtLink>
105
+ </li>
106
+ <li class="border-t border-b border-lblue">
107
+ <NuxtLink to="/contact" class="text-lg tracking-widest uppercase" @click="closeMenu">
108
+ Contact
109
+ </NuxtLink>
110
+ </li>
111
+ </ul>
112
+
113
+ <!-- Call to Action Section -->
114
+ <div class="mt-8 text-center">
115
+ <h2 class="text-3xl font-bold tracking-widest uppercase text-lblue">
116
+ Stuff
117
+ </h2>
118
+ <p class="mt-2 text-white">
119
+ More stuff here
120
+ </p>
121
+ <NuxtLink to="/contact" class="inline-block px-10 py-4 mt-4 text-lg font-bold tracking-widest underline uppercase transition text-dblue bg-burntorange hover:bg-opacity-80" @click="closeMenu">
122
+ Contact Us
123
+ </NuxtLink>
124
+ </div>
125
+ </div>
126
+ </transition>
127
+ </nav>
128
+ </template>
129
+
130
+ <style>
131
+ /* Mobile slide-in transition */
132
+ .slide-enter-active,
133
+ .slide-leave-active {
134
+ transition: transform 0.3s ease-in-out;
135
+ }
136
+ .slide-enter-from,
137
+ .slide-leave-to {
138
+ transform: translateX(100%);
139
+ }
140
+
141
+ /* Navigation Items with Triangle */
142
+ .nav-item {
143
+ position: relative;
144
+
145
+ transition: color 0.3s ease-in-out;
146
+ }
147
+
148
+ /* Hover Effect */
149
+ .nav-item:hover {
150
+ color: #A2A8AE; /* Accent color on hover */
151
+ }
152
+
153
+ .nav-item:hover::after {
154
+ transform: scale(1.3); /* Triangle expands */
155
+ background-color: white; /* Changes color slightly */
156
+ }
157
+
158
+ /* Active State */
159
+ .nav-item.router-link-active {
160
+ color: white;
161
+ font-weight: bold;
162
+ }
163
+
164
+ .nav-item.router-link-active {
165
+ color: #A2A8AE;
166
+ }
167
+ </style>
@@ -0,0 +1,63 @@
1
+ <script setup>
2
+ import { onMounted, ref } from 'vue'
3
+
4
+ defineProps({
5
+ bgImage: {
6
+ type: String,
7
+ required: true,
8
+ },
9
+ headline: {
10
+ type: String,
11
+ required: false,
12
+ default: '',
13
+ },
14
+ })
15
+
16
+ // Refs for animation
17
+ const accentImg = ref(null)
18
+ const heroHeading = ref(null)
19
+
20
+ // Animation on mount
21
+ onMounted(() => {
22
+ setTimeout(() => {
23
+ if (accentImg.value) {
24
+ accentImg.value.classList.add('opacity-100', 'translate-x-0')
25
+ }
26
+ if (heroHeading.value) {
27
+ heroHeading.value.classList.add('opacity-100', 'translate-y-0')
28
+ }
29
+ }, 300)
30
+ })
31
+ </script>
32
+
33
+ <template>
34
+ <section
35
+ class="relative z-[10] md:pt-20 md:pb-44 pb-16 px-6 flex items-center text-white text-center bg-right-bottom bg-cover md:bg-contain bg-no-repeat overflow-hidden"
36
+ :style="{ backgroundImage: `url(${bgImage})` }"
37
+ >
38
+ <!-- Ensure Text is Always on Top -->
39
+ <div class="container relative z-[20] m-auto text-left bg-opacity-50 md:pr-[50%] md:h-80">
40
+ <slot>
41
+ <h1
42
+ ref="heroHeading"
43
+ class="text-3xl md:text-6xl font-semibold !leading-snug opacity-0 translate-y-[20px] transition-all duration-1000 ease-out"
44
+ >
45
+ {{ headline }}
46
+ </h1>
47
+ </slot>
48
+ </div>
49
+
50
+ <!-- Gradient Background (top Half) -->
51
+ <div class="absolute z-10 top-0 left-0 w-full h-[128px] bg-gradient-to-b from-dblue to-transparent" />
52
+
53
+ <!-- Design Accent Image -->
54
+ <img
55
+ ref="accentImg"
56
+ src="/images/hero/titleShape.png"
57
+ alt="Decorative Accent"
58
+ class="absolute bottom-0 right-0 z-0 object-cover"
59
+ >
60
+ <!-- Diagonal Gradient Overlay -->
61
+ <div class="absolute inset-0 z-0 bg-reversed-angled-gradient" />
62
+ </section>
63
+ </template>
@@ -0,0 +1,13 @@
1
+ import antfu from '@antfu/eslint-config'
2
+
3
+ export default [
4
+ ...(await antfu()),
5
+ {
6
+ rules: {
7
+ 'vue/no-deprecated-slot-attribute': 'off',
8
+ 'curly': 'off',
9
+ 'no-console': 'off',
10
+ 'antfu/top-level-function': 'off',
11
+ },
12
+ },
13
+ ]
package/nuxt.config.ts ADDED
@@ -0,0 +1,37 @@
1
+ import { defineNuxtConfig } from 'nuxt/config'
2
+
3
+ export default defineNuxtConfig({
4
+ ssr: false,
5
+ compatibilityDate: '2024-11-01',
6
+ app: {
7
+ head: {
8
+ link: [
9
+ {
10
+ rel: 'icon',
11
+ type: 'image/x-icon',
12
+ href: '/favicon.ico',
13
+ },
14
+ {
15
+ rel: 'stylesheet',
16
+ href: 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css',
17
+ },
18
+ ],
19
+ },
20
+ },
21
+ css: [
22
+ '~/assets/css/global.css', // ✅ Keep global styles only
23
+ ],
24
+ modules: ['@nuxtjs/tailwindcss', '@vee-validate/nuxt'],
25
+ vite: {
26
+ define: {
27
+ 'process.env.DEBUG': false,
28
+ },
29
+ server: {
30
+ hmr: {
31
+ port: 3000, // Make sure this port matches your Nuxt server port
32
+ clientPort: 3000, // Ensure this matches your Nuxt server port as well
33
+ },
34
+ },
35
+ },
36
+ devtools: { enabled: false },
37
+ })
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@edgedev/create-edge-site",
3
+ "version": "1.0.0",
4
+ "description": "Create Edge Starter Site",
5
+ "bin": {
6
+ "create-edge-site": "./bin/cli.js"
7
+ },
8
+ "scripts": {
9
+ "build": "nuxt build",
10
+ "dev": "nuxt dev",
11
+ "generate": "nuxt generate",
12
+ "preview": "nuxt preview",
13
+ "postinstall": "nuxt prepare"
14
+ },
15
+ "dependencies": {
16
+ "@nuxtjs/tailwindcss": "6.13.2",
17
+ "@vee-validate/nuxt": "^4.15.0",
18
+ "@vee-validate/rules": "^4.15.0",
19
+ "@vee-validate/zod": "^4.15.0",
20
+ "nuxt": "^3.16.1",
21
+ "vue": "^3.5.13",
22
+ "vue-3-mask": "0.0.1-alpha",
23
+ "vue-router": "^4.5.0",
24
+ "vue-turnstile": "^1.0.11",
25
+ "zod": "^3.24.2"
26
+ },
27
+ "devDependencies": {
28
+ "@antfu/eslint-config": "^4.11.0",
29
+ "eslint": "^9"
30
+ }
31
+ }
@@ -0,0 +1,93 @@
1
+ <script setup>
2
+ import { toTypedSchema } from '@vee-validate/zod'
3
+ import * as zod from 'zod'
4
+
5
+ // Zod Documentation: https://zod.dev/?id=strings
6
+
7
+ const validationSchema = toTypedSchema(
8
+ zod.object({
9
+ email: zod.string({
10
+ required_error: 'Email is required',
11
+ }).min(1, { message: 'Email is required' }).email({ message: 'Must be a valid email' }),
12
+ name: zod.string({
13
+ required_error: 'Name is required',
14
+ }).min(1, { message: 'Name is required' }),
15
+ phone: zod.string({
16
+ required_error: 'Phone is required',
17
+ }).min(14, { message: 'Valid phone # required' }),
18
+ message: zod.string({
19
+ required_error: 'Message is required',
20
+ }).min(1, { message: 'Message is required' }),
21
+ }),
22
+ )
23
+
24
+ onMounted(() => {
25
+ console.log('Hello world.')
26
+ })
27
+ </script>
28
+
29
+ <template>
30
+ <Head>
31
+ <title>Edge Website - An awesome Edge website</title>
32
+ <meta name="description" content="This is an Edge website template">
33
+ <link rel="canonical" href="https://edgemarketingdesign.com/">
34
+ </Head>
35
+ <div class="min-h-[calc(100vh_-_328px)] w-full items-center justify-center flex flex-col">
36
+ <edge-form-fling
37
+ v-slot="{ submitting }"
38
+ form-fling-endpoint="https://formfling.com/s/KLm807Hz7BXhB8S08uuF-oFPhf8TuWOSPGEmUATyV-2t5gal"
39
+ turnstile-site-secret="0x4AAAAAAANxjIQsY8S7Lqur"
40
+ :validation-schema="validationSchema"
41
+ success-message="Thank you, we will be in touch soon."
42
+ error-message="There was an error submitting the form."
43
+ success-class="text-green-500"
44
+ error-class="text-red-500"
45
+ >
46
+ <Input
47
+ type="text"
48
+ placeholder="Name"
49
+ name="name"
50
+ class="w-full px-4 py-2 border border-gray-300 focus:outline-none"
51
+ error-class="text-red-500 text-sm"
52
+ />
53
+ <!-- Email and Phone -->
54
+ <div class="max-w-[400px] my-2 grid grid-cols-1 gap-4 md:grid-cols-2">
55
+ <div>
56
+ <Input
57
+ type="email"
58
+ placeholder="Email"
59
+ name="email"
60
+ class="w-full px-4 py-2 border border-gray-300 focus:outline-none"
61
+ error-class="text-red-500 text-sm"
62
+ />
63
+ </div>
64
+ <div>
65
+ <Input
66
+ type="phone"
67
+ placeholder="Phone"
68
+ name="phone"
69
+ class="w-full px-4 py-2 border border-gray-300 focus:outline-none"
70
+ error-class="text-red-500 text-sm"
71
+ />
72
+ </div>
73
+ </div>
74
+ <!-- Message -->
75
+ <div>
76
+ <Textarea
77
+ name="message"
78
+ placeholder="Message"
79
+ class="w-full h-32 px-4 py-2 mt-2 border border-gray-300 resize-none focus:outline-none"
80
+ error-class="text-red-500 text-sm"
81
+ />
82
+ </div>
83
+ <div>
84
+ <button v-if="!submitting" type="submit" class="px-6 mt-2 py-2 transition-colors bg-lblue text-dblue hover:bg-opacity-80">
85
+ Send Message
86
+ </button>
87
+ <button v-else type="button" class="px-6 mt-2 py-2 transition-colors bg-gray-300 text-gray-500 cursor-not-allowed">
88
+ Sending...
89
+ </button>
90
+ </div>
91
+ </edge-form-fling>
92
+ </div>
93
+ </template>
@@ -0,0 +1,16 @@
1
+ <script setup>
2
+ onMounted(() => {
3
+ console.log('Hello world.')
4
+ })
5
+ </script>
6
+
7
+ <template>
8
+ <Head>
9
+ <title>Edge Website - An awesome Edge website</title>
10
+ <meta name="description" content="This is an Edge website template">
11
+ <link rel="canonical" href="https://edgemarketingdesign.com/">
12
+ </Head>
13
+ <div class="min-h-[calc(100vh_-_328px)] w-full items-center justify-center flex flex-col">
14
+ <img src="/images/edge_logo.png" class="w-[400px] h-auto" alt="Edge Marketing Design Logo">
15
+ </div>
16
+ </template>
@@ -0,0 +1,11 @@
1
+ import { defineNuxtPlugin } from '#app'
2
+ import * as rules from '@vee-validate/rules'
3
+ import { defineRule } from 'vee-validate'
4
+
5
+ export default defineNuxtPlugin(() => {
6
+ for (const [ruleName, ruleFn] of Object.entries(rules)) {
7
+ if (typeof ruleFn === 'function') {
8
+ defineRule(ruleName, ruleFn)
9
+ }
10
+ }
11
+ })
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": "../.nuxt/tsconfig.server.json"
3
+ }
@@ -0,0 +1,27 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ export default {
3
+ content: [
4
+ "./components/**/*.{vue,js,ts}",
5
+ "./layouts/**/*.{vue,js,ts}",
6
+ "./pages/**/*.{vue,js,ts}",
7
+ "./app.vue",
8
+ ],
9
+ theme: {
10
+ extend: {
11
+ colors: {
12
+ dblue: "#30464C", // Dark Blue
13
+ mblue: "#46616F", // Medium Blue
14
+ lgray: "#A2A8AE", // Light Gray
15
+ lblue: "#87B4B7", // Light Blue
16
+ burntorange: "#B65B33", // Burnt Orange
17
+ tan: "#BDA86A", // Tan
18
+ cream: "#D9D0C4", // Cream
19
+ },
20
+ fontFamily: {
21
+ sans: ["Overpass", "sans-serif"],
22
+ serif: ["Rokkitt", "serif"],
23
+ },
24
+ },
25
+ },
26
+ plugins: [],
27
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": "./.nuxt/tsconfig.json"
3
+ }