knowii-oss / knowii

Knowii is a next-gen Community Knowledge Management platform
https://knowii.net
GNU Affero General Public License v3.0
10 stars 1 forks source link

Add support for profile picture and picture upload #109

Open dsebastien opened 1 year ago

dsebastien commented 1 year ago

To store user pictures:

page.props.jetstream.managesProfilePhotos page.props.auth.user.profile_photo_url

dsebastien commented 2 months ago

Profile/Partials/UpdateProfileInformationForm.tsx:

const [photoPreview, setPhotoPreview] = useState<string | null>(null);
const photoRef = useRef<HTMLInputElement>(null);
const photoInput: any = useRef(null);

...

function selectNewPhoto() {
    photoRef.current?.click();
  }

function updatePhotoPreview() {
    const photo = photoRef.current?.files?.[0];

    if (!photo) {
      return;
    }

    form.setData('photo', photo);

    const reader = new FileReader();

    reader.onload = (e) => {
      setPhotoPreview(e.target?.result as string);
    };

    reader.readAsDataURL(photo);
  }

  function deletePhoto() {
    router.delete(route('current-user-photo.destroy'), {
      preserveScroll: true,
      onSuccess: () => {
        setPhotoPreview(null);
        clearPhotoFileInput();
      },
    });
  }

  function clearPhotoFileInput() {
    if (photoRef.current?.value) {
      photoRef.current.value = '';
      form.setData('photo', null);
    }
  }

...

// in form post method:
onSuccess: () => clearPhotoFileInput(),

...

In updateProfileInformation method:
if (photoInput.value) {
    form.photo = photoInput.value.files[0];
}

...

...

{/* <!-- Profile Photo --> */}
      {page.props.jetstream.managesProfilePhotos ? (
        <div className="col-span-6 sm:col-span-4">
          {/* <!-- Profile Photo File Input --> */}
          <input type="file" className="hidden" ref={photoRef} onChange={updatePhotoPreview} />

          <InputLabel htmlFor="photo" value="Photo" />

          {photoPreview ? (
            // <!-- New Profile Photo Preview -->
            <div className="mt-2">
              <span
                className="block rounded-full w-20 h-20"
                style={{
                  backgroundSize: 'cover',
                  backgroundRepeat: 'no-repeat',
                  backgroundPosition: 'center center',
                  backgroundImage: `url('${photoPreview}')`,
                }}
              ></span>
            </div>
          ) : (
            // <!-- Current Profile Photo -->
            <div className="mt-2">
              <img src={user.profile_photo_url} alt={user.name} className="rounded-full h-20 w-20 object-cover" />
            </div>
          )}

          <SecondaryButton className="mt-2 mr-2" type="button" onClick={selectNewPhoto}>
            Select A New Photo
          </SecondaryButton>

          {user.profile_photo_path ? (
            <SecondaryButton type="button" className="mt-2" onClick={deletePhoto}>
              Remove Photo
            </SecondaryButton>
          ) : null}

          <InputError message={form.errors.photo} className="mt-2" />
        </div>
      ) : null}
dsebastien commented 2 months ago

In Profile\Partials\UpdateProfileInformationForm, vue version:

<!-- Profile Photo -->
    <div v-if="$page.props.jetstream.managesProfilePhotos" class="col-span-6 sm:col-span-4">
      <!-- Profile Photo File Input -->
      <input id="photo" ref="photoInput" type="file" class="hidden" @change="updatePhotoPreview" />

      <InputLabel for="photo" value="Photo" />

      <!-- Current Profile Photo -->
      <div v-show="!photoPreview" class="mt-2">
        <img :src="user.profile_photo_url" :alt="user.name" class="rounded-full h-20 w-20 object-cover" />
      </div>

      <!-- New Profile Photo Preview -->
      <div v-show="photoPreview" class="mt-2">
        <span
          class="block rounded-full w-20 h-20 bg-cover bg-no-repeat bg-center"
          :style="'background-image: url(\'' + photoPreview + '\');'"
        />
      </div>

      <SecondaryButton class="mt-2 me-2" type="button" @click.prevent="selectNewPhoto"> Select A New Photo </SecondaryButton>

      <SecondaryButton v-if="user.profile_photo_path" type="button" class="mt-2" @click.prevent="deletePhoto">
        Remove Photo
      </SecondaryButton>

      <InputError :message="form.errors.photo" class="mt-2" />
    </div>
dsebastien commented 2 months ago

Another version:

{jetstream.managesProfilePhotos && (
        <div className="col-span-6 sm:col-span-4">
          <input id="photo" ref={photoInput} type="file" className="hidden" onChange={updatePhotoPreview} />

          <InputLabel htmlFor="photo" value="Photo" />

          {!photoPreview ? (
            <div className="mt-2">
              <img src={user.profile_photo_url} alt={user.name} className="object-cover w-20 h-20 rounded-full" />
            </div>
          ) : (
            <div className="mt-2">
              <span
                className="block w-20 h-20 bg-center bg-no-repeat bg-cover rounded-full"
                style={{
                  backgroundImage: `url('${photoPreview}')`,
                }}
              />
            </div>
          )}

          <SecondaryButton className="mt-2 me-2" type="button" onClick={selectNewPhoto}>
            Select A New Photo
          </SecondaryButton>

          {user.profile_photo_path && (
            <SecondaryButton type="button" className="mt-2" onClick={deletePhoto}>
              Remove Photo
            </SecondaryButton>
          )}

          <InputError message={form.errors.photo} className="mt-2" />
        </div>
      )}
dsebastien commented 3 weeks ago

Make sure to protect against malicious image uploads: https://medium.com/@dsjayamal/8-security-best-practices-in-laravel-ad7513798cfb