diff --git a/app/Http/Controllers/TemplateController.php b/app/Http/Controllers/TemplateController.php index 2f60c9b..474d0ad 100644 --- a/app/Http/Controllers/TemplateController.php +++ b/app/Http/Controllers/TemplateController.php @@ -7,6 +7,7 @@ use App\Http\Requests\Templates\FormTemplateRequest; use App\Http\Resources\FormTemplateResource; use App\Models\Template; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Auth; class TemplateController extends Controller { @@ -16,12 +17,16 @@ class TemplateController extends Controller if ($request->offsetExists('limit') && $request->get('limit') > 0) { $limit = (int) $request->get('limit'); } - return FormTemplateResource::collection( - Template::where('publicly_listed', true) - ->orderByDesc('created_at') - ->limit($limit) - ->get() - ); + + $templates = Template::where('publicly_listed', true) + ->when(Auth::check(), function ($query) { + $query->orWhere('creator_id', Auth::id()); + }) + ->orderByDesc('created_at') + ->limit($limit) + ->get(); + + return FormTemplateResource::collection($templates); } public function create(FormTemplateRequest $request) @@ -34,7 +39,8 @@ class TemplateController extends Controller return $this->success([ 'message' => 'Template was created.', - 'template_id' => $template->id + 'template_id' => $template->id, + 'data' => new FormTemplateResource($template) ]); } diff --git a/app/Http/Requests/Templates/FormTemplateRequest.php b/app/Http/Requests/Templates/FormTemplateRequest.php index 60436fe..048377c 100644 --- a/app/Http/Requests/Templates/FormTemplateRequest.php +++ b/app/Http/Requests/Templates/FormTemplateRequest.php @@ -77,6 +77,7 @@ class FormTemplateRequest extends FormRequest } return new Template([ + 'creator_id' => $this->user()?->id ?? null, 'publicly_listed' => $this->publicly_listed, 'name' => $this->name, 'slug' => $this->slug, diff --git a/app/Models/Template.php b/app/Models/Template.php index 22d5c5f..59c42e3 100644 --- a/app/Models/Template.php +++ b/app/Models/Template.php @@ -14,6 +14,7 @@ class Template extends Model use HasFactory, HasSlug; protected $fillable = [ + 'creator_id', 'name', 'slug', 'description', @@ -41,6 +42,15 @@ class Template extends Model 'publicly_listed' => false, ]; + protected $appends = [ + 'share_url', + ]; + + public function getShareUrlAttribute() + { + return url('/form-templates/'.$this->slug); + } + public function setDescriptionAttribute($value) { // Strip out unwanted html diff --git a/app/Models/User.php b/app/Models/User.php index 1161aa2..538136b 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -4,6 +4,7 @@ namespace App\Models; use App\Http\Controllers\SubscriptionController; use App\Models\Forms\Form; +use App\Models\Template; use App\Notifications\ResetPassword; use App\Notifications\VerifyEmail; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -140,6 +141,11 @@ class User extends Authenticatable implements JWTSubject return $this->hasMany(Form::class,'creator_id'); } + public function formTemplates() + { + return $this->hasMany(Template::class, 'creator_id'); + } + /** * ================================= * Oauth Related diff --git a/app/Policies/TemplatePolicy.php b/app/Policies/TemplatePolicy.php index 9a46201..4ad9a94 100644 --- a/app/Policies/TemplatePolicy.php +++ b/app/Policies/TemplatePolicy.php @@ -18,7 +18,7 @@ class TemplatePolicy */ public function create(User $user) { - return $user->admin || $user->template_editor; + return $user !== null; } /** @@ -30,7 +30,7 @@ class TemplatePolicy */ public function update(User $user, Template $template) { - return $user->admin || $user->template_editor; + return $user->admin || $user->template_editor || $template->creator_id === $user->id; } /** @@ -42,6 +42,6 @@ class TemplatePolicy */ public function delete(User $user, Template $template) { - return $user->admin || $user->template_editor; + return $user->admin || $user->template_editor || $template->creator_id === $user->id; } } diff --git a/database/migrations/2023_09_21_092812_add_creator_id_to_templates.php b/database/migrations/2023_09_21_092812_add_creator_id_to_templates.php new file mode 100644 index 0000000..b35a1b1 --- /dev/null +++ b/database/migrations/2023_09_21_092812_add_creator_id_to_templates.php @@ -0,0 +1,32 @@ +foreignIdFor(\App\Models\User::class,'creator_id')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('templates', function (Blueprint $table) { + $table->dropColumn('creator_id'); + }); + } +}; diff --git a/resources/js/components/Navbar.vue b/resources/js/components/Navbar.vue index 5f766ee..a8dadb7 100644 --- a/resources/js/components/Navbar.vue +++ b/resources/js/components/Navbar.vue @@ -68,6 +68,15 @@ My Forms + + + + + My Templates + + diff --git a/resources/js/components/open/forms/components/templates/FormTemplateModal.vue b/resources/js/components/open/forms/components/templates/FormTemplateModal.vue index 0040818..0ce7f8a 100644 --- a/resources/js/components/open/forms/components/templates/FormTemplateModal.vue +++ b/resources/js/components/open/forms/components/templates/FormTemplateModal.vue @@ -18,13 +18,13 @@ - New template will be create from your form {{ form.title }}. + New template will be create from your form: {{ form.title }}. - + import Form from 'vform' import store from '~/store' -import { mapState } from 'vuex' +import { mapState, mapGetters } from 'vuex' import axios from 'axios' import QuestionsEditor from './QuestionsEditor.vue' @@ -93,7 +93,7 @@ export default { mounted () { this.templateForm = new Form(this.template ?? { - publicly_listed: true, + publicly_listed: false, name: '', slug: '', short_description: '', @@ -113,6 +113,9 @@ export default { industries: state => state['open/templates'].industries, types: state => state['open/templates'].types }), + ...mapGetters({ + user: 'auth/user' + }), typesOptions () { return Object.values(this.types).map((type) => { return { @@ -153,6 +156,7 @@ export default { if (response.data.message) { this.alertSuccess(response.data.message) } + this.$store.commit('open/templates/addOrUpdate', response.data.data) this.$emit('close') }) }, @@ -162,7 +166,7 @@ export default { if (response.data.message) { this.alertSuccess(response.data.message) } - this.$store.dispatch('open/templates/addOrUpdate', response.data.data) + this.$store.commit('open/templates/addOrUpdate', response.data.data) this.$emit('close') }) }, @@ -173,7 +177,7 @@ export default { this.alertSuccess(response.data.message) } this.$router.push({ name: 'templates' }) - this.$store.dispatch('open/templates/remove', response.data.data) + this.$store.commit('open/templates/remove', this.template) this.$emit('close') }) } diff --git a/resources/js/components/pages/forms/show/ExtraMenu.vue b/resources/js/components/pages/forms/show/ExtraMenu.vue index 4c56933..d59a55a 100644 --- a/resources/js/components/pages/forms/show/ExtraMenu.vue +++ b/resources/js/components/pages/forms/show/ExtraMenu.vue @@ -20,7 +20,7 @@ - @@ -67,7 +67,7 @@ Duplicate form - @@ -117,7 +117,7 @@ - + diff --git a/resources/js/components/pages/pricing/CheckoutDetailsModal.vue b/resources/js/components/pages/pricing/CheckoutDetailsModal.vue index f2645be..251bccb 100644 --- a/resources/js/components/pages/pricing/CheckoutDetailsModal.vue +++ b/resources/js/components/pages/pricing/CheckoutDetailsModal.vue @@ -44,7 +44,7 @@ export default { watch: { user () { - this.form.email = this.user.email + this.updateUser() }, show () { // Wait for modal to open and focus on first field @@ -59,13 +59,16 @@ export default { }, mounted () { - if (this.user) { - this.form.name = this.user.name - this.form.email = this.user.email - } + this.updateUser() }, methods: { + updateUser() { + if (this.user) { + this.form.name = this.user.name + this.form.email = this.user.email + } + }, saveDetails () { if (this.form.busy) return this.form.put('api/subscription/update-customer-details').then(() => { diff --git a/resources/js/components/pages/templates/TemplatesList.vue b/resources/js/components/pages/templates/TemplatesList.vue new file mode 100644 index 0000000..f3e0258 --- /dev/null +++ b/resources/js/components/pages/templates/TemplatesList.vue @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + No templates found. + + + + + + + + + + + diff --git a/resources/js/pages/templates/my_templates.vue b/resources/js/pages/templates/my_templates.vue new file mode 100644 index 0000000..78d4a25 --- /dev/null +++ b/resources/js/pages/templates/my_templates.vue @@ -0,0 +1,44 @@ + + + + + + + My Form Templates + + + Share your best form as templates so that others can re-use them! + + + + + + + + + + diff --git a/resources/js/pages/templates/show.vue b/resources/js/pages/templates/show.vue index f0fa95c..204c79a 100644 --- a/resources/js/pages/templates/show.vue +++ b/resources/js/pages/templates/show.vue @@ -2,7 +2,7 @@ - + Edit Template @@ -12,6 +12,11 @@ + + Copy Template URL + @@ -228,6 +233,16 @@ export default { cleanQuotes (str) { // Remove starting and ending quotes if any return (str) ? str.replace(/^"/, '').replace(/"$/, '') : '' + }, + copyTemplateUrl(){ + const str = this.template.share_url + const el = document.createElement('textarea') + el.value = str + document.body.appendChild(el) + el.select() + document.execCommand('copy') + document.body.removeChild(el) + this.alertSuccess('Copied!') } }, @@ -251,6 +266,9 @@ export default { form () { return this.template ? new Form(this.template.structure) : null }, + canEditTemplate () { + return this.user && this.template && (this.user.admin || this.user.template_editor || this.template.creator_id === this.user.id) + }, metaTitle () { return this.template ? this.template.name : 'Form Template' }, diff --git a/resources/js/pages/templates/templates.vue b/resources/js/pages/templates/templates.vue index 7894de5..095a545 100644 --- a/resources/js/pages/templates/templates.vue +++ b/resources/js/pages/templates/templates.vue @@ -13,147 +13,34 @@ - - - - - - - - - - - - - - - - - - - - - No templates found. - - - - - - - - +
- New template will be create from your form {{ form.title }}. + New template will be create from your form: {{ form.title }}.
+ No templates found. +
+ Share your best form as templates so that others can re-use them! +
- No templates found. -